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@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 = ¤t_packet; + PACKET* new_pkt = event->tablet_event.packet; + int event_type = event->tablet_event.type; + BOOL is_proximity = (event_type != TABLET_EVENT_POINT); + + if (is_proximity) + { + /* Wacom's documentation very confusingly states: + * > The high-order word is non-zero when the cursor is leaving or + * > entering hardware proximity. + * What it actually means is that the high-order word is 1 when entering + * proximity or 0 when leaving proximity. + * + * The low and high order words are always the same. The only way to + * enter/leave context proximity without entering/leaving device + * proximity is to have multiple contexts. Macdrv has no awareness of + * contexts so this would have to be handled by wintab.dll. */ + BOOL is_enter = (event_type == TABLET_EVENT_PROXIMITY_ENTER); + LPARAM l_param = MAKELPARAM(is_enter, is_enter); + + /* We can get duplicate events due to NSEventTypeMouseMoved + + * NSEventTypeTabletProximity both being delivered. Squash them here. */ + if (last_event_type == event_type) + return; + + last_event_type = event_type; + + for (i = 0; i < registered_hwnd_count; i++) + { + HWND hwnd = registered_hwnd[i]; + SendMessageW(macdrv_tablet_hwnd, WT_PROXIMITY, (WPARAM)hwnd, l_param); + } + } + else + { + last_event_type = TABLET_EVENT_POINT; + + /* Calculate which values changed */ + new_pkt->pkChanged = ((new_pkt->pkStatus != old_pkt->pkStatus) * PK_STATUS) + | ((new_pkt->pkTime != old_pkt->pkTime) * PK_TIME) + | PK_SERIAL_NUMBER + | ((new_pkt->pkButtons != old_pkt->pkButtons) * PK_BUTTONS) + | ((new_pkt->pkX != old_pkt->pkX) * PK_X) + | ((new_pkt->pkY != old_pkt->pkY) * PK_Y) + | ((new_pkt->pkZ != old_pkt->pkZ) * PK_Z) + | ((new_pkt->pkNormalPressure != old_pkt->pkNormalPressure) * PK_NORMAL_PRESSURE) + | ((new_pkt->pkTangentPressure != old_pkt->pkTangentPressure) * PK_TANGENT_PRESSURE) + | ((memcmp(&new_pkt->pkOrientation, &old_pkt->pkOrientation, sizeof(new_pkt->pkOrientation)) != 0) * PK_ORIENTATION); + + *old_pkt = *new_pkt; + free(new_pkt); + + for (i = 0; i < registered_hwnd_count; i++) + { + HWND hwnd = registered_hwnd[i]; + SendMessageW(macdrv_tablet_hwnd, WT_PACKET, (WPARAM)current_packet.pkSerialNumber, (LPARAM)hwnd); + } + } +} + +int CDECL macdrv_GetCurrentPacket(LPPACKET packet){ + *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/wc... - * - * 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-... */
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/wc... + * + * 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 *
Wine apps no longer accept the first mouse click when the application is inactive. This mirrors the expected behavior of Mac apps and prevents a bug where focus is never received.
Call SetActiveWindow(NULL) on deactivate to prevent a bug where focus is never lost.
Signed-off-by: Elaine Lefler elaineclefler@gmail.com ---
Normally I would put the mouse-down behavior under a toggle, but the window focus really is broken without it. I'm not sure why, Wine seems to be doing everything right, but the message gets eaten somewhere between the Mac side and the Windows side and this is the only way to fix it.
Most users should find this behavior preferable, as the vast majority of Mac apps do the same thing, and it's annoying when an application registers a click that you expected it to ignore. --- dlls/winemac.drv/cocoa_app.m | 9 ++++++--- dlls/winemac.drv/window.c | 1 + 2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m index b5a3059382e..8c525333e8d 100644 --- a/dlls/winemac.drv/cocoa_app.m +++ b/dlls/winemac.drv/cocoa_app.m @@ -1524,15 +1524,18 @@ - (void) handleMouseButton:(NSEvent*)theEvent { if (mouseCaptureWindow) process = TRUE; - else + /* Don't deliver mouse-down to an application that isn't active. + * This mirrors macOS's behavior and delivering the mouse event + * before activate can make the window fail to receive focus. */ + else if ([[NSApplication sharedApplication] isActive]) { - // Test if the click was in the window's content area. + /* Test if the click was in the window's content area. */ NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)]; NSRect contentRect = [window contentRectForFrameRect:[window frame]]; process = NSMouseInRect(nspoint, contentRect, NO); if (process && [window styleMask] & NSWindowStyleMaskResizable) { - // Ignore clicks in the grow box (resize widget). + /* Ignore clicks in the grow box (resize widget). */ HIPoint origin = { 0, 0 }; HIThemeGrowBoxDrawInfo info = { 0 }; HIRect bounds; diff --git a/dlls/winemac.drv/window.c b/dlls/winemac.drv/window.c index 4f3dbc08311..9177f493a5f 100644 --- a/dlls/winemac.drv/window.c +++ b/dlls/winemac.drv/window.c @@ -2415,6 +2415,7 @@ void macdrv_app_deactivated(void) TRACE("setting fg to desktop\n"); SetForegroundWindow(GetDesktopWindow()); } + SetActiveWindow(NULL); }
Signed-off-by: Elaine Lefler elaineclefler@gmail.com ---
Currently GDI apps are unusably slow, especially on Retina displays. Running PaintTool SAI on my 2017 Macbook Pro, I'm only able to achieve 4 FPS when drawing or panning the canvas. This change brings it up to the full 60 Hz on the same hardware.
This diff is slightly smaller if compared to wine-6.16, the last version where drawRect: appeared. Returning to this method is more performant as long as it's implemented properly. Some retina code has been removed because it's not necessary when the view isn't rolling its own layer. The ability to draw multiple rectangles indepedently has been removed, as this use case is vanishingly rare and tracking a non- rectangular region hurts performance in the much more common case of an app painting one large rectangle.
wineWindows is now an instance variable because we were maintaining this list anyway and polling the window server can cause cursor lag. A custom hit-test is more efficient and allows us to work around issues with asychronous drawing. The WineWindow class no longer stores the reference to surface->mutex, as CGImageCreate can take a non-trivial amount of time. The lock is more appropriately moved into create_surface_image - at which point, the instance variable is no longer used. --- dlls/winemac.drv/cocoa_app.h | 2 + dlls/winemac.drv/cocoa_app.m | 59 +++- dlls/winemac.drv/cocoa_window.h | 6 +- dlls/winemac.drv/cocoa_window.m | 290 ++++++++++-------- dlls/winemac.drv/macdrv.h | 1 - dlls/winemac.drv/macdrv_cocoa.h | 13 +- dlls/winemac.drv/surface.c | 527 +++++++++++++++++++++++++------- dlls/winemac.drv/window.c | 4 +- 8 files changed, 643 insertions(+), 259 deletions(-)
diff --git a/dlls/winemac.drv/cocoa_app.h b/dlls/winemac.drv/cocoa_app.h index 52c91c0621f..b55c55f2995 100644 --- a/dlls/winemac.drv/cocoa_app.h +++ b/dlls/winemac.drv/cocoa_app.h @@ -124,6 +124,8 @@ @interface WineApplicationController : NSObject <NSApplicationDelegate>
id<WineClipCursorHandler> clipCursorHandler;
+ NSMutableArray* wineWindows; + NSImage* applicationIcon;
BOOL beenActive; diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m index 8c525333e8d..e8b88286d24 100644 --- a/dlls/winemac.drv/cocoa_app.m +++ b/dlls/winemac.drv/cocoa_app.m @@ -47,6 +47,25 @@ + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows; #endif
+@interface NSWindow (WineNSPointExtensions) + +/* Reimplementation of -convertPointFromScreen: which isn't available on all + * supported macOS versions */ +- (NSPoint)wineConvertPointFromScreen:(NSPoint)point; + +@end + +@implementation NSWindow (WineNSPointExtensions) + +- (NSPoint)wineConvertPointFromScreen:(NSPoint)point +{ + NSPoint origin = self.frame.origin; + return NSMakePoint(point.x - origin.x, point.y - origin.y); +} + +@end + + /*********************************************************************** * WineLocalizedString * @@ -204,6 +223,7 @@ - (void) dealloc [keyWindows release]; [eventQueues release]; [eventQueuesLock release]; + [wineWindows release]; if (requestsManipQueue) dispatch_release(requestsManipQueue); [requests release]; if (requestSource) @@ -571,7 +591,6 @@ - (WineWindow*) frontWineWindow - (void) adjustWindowLevels:(BOOL)active { NSArray* windowNumbers; - NSMutableArray* wineWindows; NSNumber* windowNumber; NSUInteger nextFloatingIndex = 0; __block NSInteger maxLevel = NSIntegerMin; @@ -582,6 +601,8 @@ - (void) adjustWindowLevels:(BOOL)active
if ([NSApp isHidden]) return;
+ [wineWindows release]; + windowNumbers = [NSWindow windowNumbersWithOptions:0]; wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
@@ -652,8 +673,6 @@ - (void) adjustWindowLevels:(BOOL)active
NSEnableScreenUpdates();
- [wineWindows release]; - // The above took care of the visible windows on the current space. That // leaves windows on other spaces, minimized windows, and windows which // are not ordered in. We want to leave windows on other spaces alone @@ -1337,19 +1356,33 @@ - (void) handleMouseMove:(NSEvent*)anEvent targetWindow = (WineWindow*)[anEvent window]; else { - /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the - event indicates its window is the main window, even if the cursor is - over a different window. Find the actual WineWindow that is under the - cursor and post the event as being for that window. */ + /* Due to our use of NSTrackingArea and the way Cocoa directs mouse + * moves, the window receiving the event is probably not the one + * with the cursor. Find the window that actually has the cursor by + * hit-testing front to back. */ CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]); NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)]; - NSInteger windowUnderNumber; + WineWindow* window;
- windowUnderNumber = [NSWindow windowNumberAtPoint:point - belowWindowWithWindowNumber:0]; - targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber]; - if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO)) - targetWindow = nil; + targetWindow = nil; + + for (window in wineWindows) + { + NSPoint windowPoint = [window wineConvertPointFromScreen:point]; + BOOL isHit = ([window.contentView hitTest:windowPoint] != nil); + + /* Windows with transparency must be instructed to ignore + * mouse-downs when the hovered pixel is not visible. The + * window's tracking area still reports events. */ + if (window.needsTransparency) + [window setIgnoresMouseEvents:!isHit && NSMouseInRect(windowPoint, window.contentView.frame, NO)]; + + if (isHit) + { + targetWindow = (WineWindow*)window; + break; + } + } }
if ([targetWindow isKindOfClass:[WineWindow class]]) diff --git a/dlls/winemac.drv/cocoa_window.h b/dlls/winemac.drv/cocoa_window.h index a83f2aa803b..675abcc659b 100644 --- a/dlls/winemac.drv/cocoa_window.h +++ b/dlls/winemac.drv/cocoa_window.h @@ -45,7 +45,6 @@ @interface WineWindow : NSPanel <NSWindowDelegate> WineEventQueue* queue;
void* surface; - pthread_mutex_t* surface_mutex;
CGDirectDisplayID _lastDisplayID; NSTimeInterval _lastDisplayTime; @@ -53,10 +52,11 @@ @interface WineWindow : NSPanel <NSWindowDelegate> NSRect wineFrame; NSRect roundedWineFrame;
+ NSData* shapeData; BOOL shapeChangedSinceLastDraw;
BOOL colorKeyed; - CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue; + uint8_t colorKeyRed, colorKeyGreen, colorKeyBlue;
BOOL usePerPixelAlpha;
@@ -94,6 +94,8 @@ @interface WineWindow : NSPanel <NSWindowDelegate> @property (readonly, nonatomic) BOOL noForeground; @property (readonly, nonatomic) BOOL preventsAppActivation; @property (readonly, nonatomic) BOOL floating; +@property (readonly, nonatomic) BOOL needsTransparency; +@property (readonly, nonatomic) BOOL needsLayerTransparency; @property (readonly, getter=isFullscreen, nonatomic) BOOL fullscreen; @property (readonly, getter=isFakingClose, nonatomic) BOOL fakingClose; @property (readonly, nonatomic) NSRect wine_fractionalFrame; diff --git a/dlls/winemac.drv/cocoa_window.m b/dlls/winemac.drv/cocoa_window.m index bfa7e2fe8cc..74a84aab630 100644 --- a/dlls/winemac.drv/cocoa_window.m +++ b/dlls/winemac.drv/cocoa_window.m @@ -309,32 +309,6 @@ static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTi @end
-#ifndef MAC_OS_X_VERSION_10_14 -@protocol NSViewLayerContentScaleDelegate <NSObject> -@optional - - - (BOOL) layer:(CALayer*)layer shouldInheritContentsScale:(CGFloat)newScale fromWindow:(NSWindow*)window; - -@end -#endif - - -@interface CAShapeLayer (WineShapeMaskExtensions) - -@property(readonly, nonatomic, getter=isEmptyShaped) BOOL emptyShaped; - -@end - -@implementation CAShapeLayer (WineShapeMaskExtensions) - - - (BOOL) isEmptyShaped - { - return CGRectEqualToRect(CGPathGetBoundingBox(self.path), CGRectZero); - } - -@end - - @interface WineBaseView : NSView @end
@@ -351,10 +325,11 @@ - (id) initWithFrame:(NSRect)frame device:(id<MTLDevice>)device; #endif
-@interface WineContentView : WineBaseView <NSTextInputClient, NSViewLayerContentScaleDelegate> +@interface WineContentView : WineBaseView <NSTextInputClient> { NSMutableArray* glContexts; NSMutableArray* pendingGlContexts; + BOOL _shouldBeHidden; BOOL _everHadGLContext; BOOL _cachedHasGLDescendant; BOOL _cachedHasGLDescendantValid; @@ -363,7 +338,6 @@ @interface WineContentView : WineBaseView <NSTextInputClient, NSViewLayerContent NSMutableAttributedString* markedText; NSRange markedTextSelection;
- BOOL _retinaMode; int backingSize[2];
#ifdef HAVE_METAL_METAL_H @@ -402,13 +376,12 @@ @interface WineWindow () @property (retain, readwrite, nonatomic) WineEventQueue* queue;
@property (nonatomic) void* surface; -@property (nonatomic) pthread_mutex_t* surface_mutex;
+@property (retain, nonatomic) NSData* shapeData; @property (nonatomic) BOOL shapeChangedSinceLastDraw; -@property (readonly, nonatomic) BOOL needsTransparency;
@property (nonatomic) BOOL colorKeyed; -@property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue; +@property (nonatomic) uint8_t colorKeyRed, colorKeyGreen, colorKeyBlue; @property (nonatomic) BOOL usePerPixelAlpha;
@property (assign, nonatomic) void* imeData; @@ -416,8 +389,6 @@ @interface WineWindow ()
@property (readonly, copy, nonatomic) NSArray* childWineWindows;
- - (void) setShape:(CGPathRef)newShape; - - (void) updateForGLSubviews;
- (BOOL) becameEligibleParentOrChild; @@ -491,57 +462,76 @@ - (BOOL) isFlipped return YES; }
- - (BOOL) wantsUpdateLayer + - (BOOL) wantsDefaultClipping + { + /* Don't need this, we already limit our drawing to the dirty region */ + return NO; + } + + - (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event { - return YES /*!_everHadGLContext*/; + /* Returning NSNull ensures the layer will never animate itself */ + return [NSNull null]; }
- - (void) updateLayer + - (BOOL) isOpaque { WineWindow* window = (WineWindow*)[self window]; - CGImageRef image = NULL; - CGRect imageRect; - CALayer* layer = [self layer]; + return window.contentView == self && !window.needsLayerTransparency; + }
- if ([window contentView] != self) - return; + - (NSView*) hitTest:(NSPoint)point + { + WineWindow* window = (WineWindow*)[self window]; + NSPoint localPoint; + CGPoint cgPoint;
- if (window.closing || !window.surface || !window.surface_mutex) - return; + if (window.contentView != self + || (!window.shapeData && !window.needsLayerTransparency)) + return [super hitTest:point];
- pthread_mutex_lock(window.surface_mutex); - if (get_surface_blit_rects(window.surface, NULL, NULL)) - { - imageRect = layer.bounds; - imageRect.origin.x *= layer.contentsScale; - imageRect.origin.y *= layer.contentsScale; - imageRect.size.width *= layer.contentsScale; - imageRect.size.height *= layer.contentsScale; - image = create_surface_image(window.surface, &imageRect, FALSE, window.colorKeyed, - window.colorKeyRed, window.colorKeyGreen, window.colorKeyBlue); - } - pthread_mutex_unlock(window.surface_mutex); + localPoint = [self convertPoint:point fromView:self.superview]; + cgPoint = cgpoint_win_from_mac(NSPointToCGPoint(localPoint));
- if (image) + if (window.shapeData) { - layer.contents = (id)image; - CFRelease(image); - [window windowDidDrawContent]; + NSPoint nsPoint = NSPointFromCGPoint(cgPoint); + const CGRect* rects = (const CGRect*)window.shapeData.bytes; + NSUInteger count = window.shapeData.length / sizeof(*rects); + BOOL inShape = NO; + NSUInteger i;
- // If the window may be transparent, then we have to invalidate the - // shadow every time we draw. Also, if this is the first time we've - // drawn since changing from transparent to opaque. - if (window.colorKeyed || window.usePerPixelAlpha || window.shapeChangedSinceLastDraw) + for (i = 0; i < count; i++) { - window.shapeChangedSinceLastDraw = FALSE; - [window invalidateShadow]; + if (NSMouseInRect(nsPoint, NSRectFromCGRect(rects[i]), NO)) + { + inShape = YES; + break; + } } + + if (!inShape) + return nil; } + + if (window.needsLayerTransparency) + { + /* Transparent pixels are not supposed to be clickable, but due to + * contentView.layer.drawsAsynchronously, Cocoa does not enforce it. + * Therefore, we must perform our own per-pixel hit test. */ + if (!surface_hit_test(window.surface, cgPoint, window.colorKeyed, + window.colorKeyRed, window.colorKeyGreen, window.colorKeyBlue)) + return nil; + } + + return [super hitTest:point]; }
- - (void) viewWillDraw + - (void) drawRect:(NSRect)rect { - [super viewWillDraw]; + WineWindow* window = (WineWindow*)[self window]; + CGRect imageRect = cgrect_win_from_mac(NSRectToCGRect(rect)); + CGImageRef image;
for (WineOpenGLContext* context in pendingGlContexts) { @@ -554,6 +544,46 @@ - (void) viewWillDraw } [glContexts addObjectsFromArray:pendingGlContexts]; [pendingGlContexts removeAllObjects]; + + if ([window contentView] != self) + return; + + if ((image = create_surface_image(window.surface, &imageRect, window.colorKeyed, + window.colorKeyRed, window.colorKeyGreen, window.colorKeyBlue)) != NULL) + { + CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextSetBlendMode(context, kCGBlendModeCopy); + /* HQ interpolation should be used with retina to prevent artifacts + * on mixed DPI. Not needed for standard DPI. */ + CGContextSetInterpolationQuality(context, retina_on ? kCGInterpolationHigh : kCGInterpolationNone); + + CGContextDrawImage(context, cgrect_mac_from_win(imageRect), image); + CGImageRelease(image); + + [window windowDidDrawContent]; + } + + /* If the window may be transparent, then we have to invalidate the + * shadow every time we draw. Also, if this is the first time we've + * drawn since changing from transparent to opaque. */ + if (window.drawnSinceShown && (window.colorKeyed || window.usePerPixelAlpha || window.shapeChangedSinceLastDraw)) + { + window.shapeChangedSinceLastDraw = FALSE; + [window invalidateShadow]; + } + } + + - (void) setHidden:(BOOL)hidden + { + if (self.window.contentView == self) + { + [super setHidden:hidden]; + return; + } + + /* Client views should always remain hidden, unless we have OpenGL */ + [super setHidden:hidden || !_everHadGLContext]; + _shouldBeHidden = hidden; }
- (void) addGLContext:(WineOpenGLContext*)context @@ -582,7 +612,10 @@ - (void) addGLContext:(WineOpenGLContext*)context
_everHadGLContext = YES; if (!hadContext) + { + [super setHidden:_shouldBeHidden]; [self invalidateHasGLDescendant]; + } [(WineWindow*)[self window] updateForGLSubviews]; }
@@ -686,18 +719,9 @@ - (void) setRetinaMode:(int)mode [self setWantsBestResolutionOpenGLSurface:mode]; [self updateGLContexts];
- _retinaMode = !!mode; - [self layer].contentsScale = mode ? 2.0 : 1.0; - [self layer].minificationFilter = mode ? kCAFilterLinear : kCAFilterNearest; - [self layer].magnificationFilter = mode ? kCAFilterLinear : kCAFilterNearest; [super setRetinaMode:mode]; }
- - (BOOL) layer:(CALayer*)layer shouldInheritContentsScale:(CGFloat)newScale fromWindow:(NSWindow*)window - { - return (_retinaMode || newScale == 1.0); - } - - (void) viewDidHide { [super viewDidHide]; @@ -957,8 +981,8 @@ @implementation WineWindow
@synthesize disabled, noForeground, preventsAppActivation, floating, fullscreen, fakingClose, closing, latentParentWindow, hwnd, queue; @synthesize drawnSinceShown; - @synthesize surface, surface_mutex; - @synthesize shapeChangedSinceLastDraw; + @synthesize surface; + @synthesize shapeData, shapeChangedSinceLastDraw; @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue; @synthesize usePerPixelAlpha; @synthesize imeData, commandDone; @@ -991,7 +1015,6 @@ + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)w [window disableCursorRects]; [window setShowsResizeIndicator:NO]; [window setHasShadow:wf->shadow]; - [window setAcceptsMouseMovedEvents:YES]; [window setDelegate:window]; [window setBackgroundColor:[NSColor clearColor]]; [window setOpaque:NO]; @@ -1010,12 +1033,10 @@ + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)w if (!contentView) return nil; [contentView setWantsLayer:YES]; - [contentView layer].minificationFilter = retina_on ? kCAFilterLinear : kCAFilterNearest; - [contentView layer].magnificationFilter = retina_on ? kCAFilterLinear : kCAFilterNearest; - [contentView layer].contentsScale = retina_on ? 2.0 : 1.0; + [contentView.layer setDrawsAsynchronously:YES]; [contentView setAutoresizesSubviews:NO];
- /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES + /* We use tracking areas instead of setAcceptsMouseMovedEvents:YES because they give us mouse moves in the background. */ trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds] options:(NSTrackingMouseMoved | @@ -1062,6 +1083,7 @@ - (void) dealloc [queue release]; [latentChildWindows release]; [latentParentWindow release]; + [shapeData release]; [super dealloc]; }
@@ -2008,47 +2030,80 @@ - (void) setDisabled:(BOOL)newValue } }
+ - (BOOL) needsLayerTransparency + { + return self.colorKeyed || self.usePerPixelAlpha; + } + - (BOOL) needsTransparency { - return self.contentView.layer.mask || self.colorKeyed || self.usePerPixelAlpha || + return self.shapeData || self.needsLayerTransparency || (gl_surface_mode == GL_SURFACE_BEHIND && [(WineContentView*)self.contentView hasGLDescendant]); }
- (void) checkTransparency { - if (![self isOpaque] && !self.needsTransparency) + if (!self.opaque && !self.needsTransparency) { self.shapeChangedSinceLastDraw = TRUE; - [[self contentView] setNeedsDisplay:YES]; + [self.contentView setNeedsDisplay:YES]; [self setBackgroundColor:[NSColor windowBackgroundColor]]; [self setOpaque:YES]; + /* Ensure WineApplicationController hasn't cut off mouse events */ + [self setIgnoresMouseEvents:NO]; } - else if ([self isOpaque] && self.needsTransparency) + else if (self.opaque && self.needsTransparency) { self.shapeChangedSinceLastDraw = TRUE; - [[self contentView] setNeedsDisplay:YES]; + [self.contentView setNeedsDisplay:YES]; [self setBackgroundColor:[NSColor clearColor]]; [self setOpaque:NO]; } + + [self.contentView.layer setOpaque:!self.needsLayerTransparency]; }
- - (void) setShape:(CGPathRef)newShape + - (void) setShapeData:(NSData*)newShapeData { - CALayer* layer = [[self contentView] layer]; - CAShapeLayer* mask = (CAShapeLayer*)layer.mask; - if (CGPathEqualToPath(newShape, mask.path)) return; + if (shapeData == newShapeData) + return; + + if ([newShapeData length] == 0) + newShapeData = nil; + + if (shapeData) + { + CGRect boundingBox = CGPathGetBoundingBox([(CAShapeLayer*)self.contentView.layer.mask path]); + [shapeData release]; + [self.contentView setNeedsDisplayInRect:NSRectFromCGRect(boundingBox)]; + } + + if (newShapeData) + { + const CGRect* rects = (const CGRect*)newShapeData.bytes; + NSUInteger count = newShapeData.length / sizeof(*rects); + NSUInteger i; + + CGMutablePathRef path = CGPathCreateMutable(); + CAShapeLayer* maskLayer;
- if (newShape && !layer.mask) - layer.mask = mask = [CAShapeLayer layer]; - else if (!newShape) - layer.mask = mask = nil; + for (i = 0; i < count; i++) + CGPathAddRect(path, nil, cgrect_mac_from_win(rects[i]));
- if (mask.path) - [[self contentView] setNeedsDisplayInRect:NSRectFromCGRect(CGPathGetBoundingBox(mask.path))]; - if (newShape) - [[self contentView] setNeedsDisplayInRect:NSRectFromCGRect(CGPathGetBoundingBox(newShape))]; + maskLayer = [CAShapeLayer layer]; + maskLayer.path = path; + self.contentView.layer.mask = maskLayer;
- mask.path = newShape; + [self.contentView setNeedsDisplayInRect:NSRectFromCGRect(CGPathGetBoundingBox(path))]; + + CFRelease(path); + } + else + { + self.contentView.layer.mask = nil; + } + + shapeData = [newShapeData retain]; self.shapeChangedSinceLastDraw = TRUE;
[self checkTransparency]; @@ -2250,8 +2305,7 @@ - (void) checkWineDisplayLink
- (BOOL) isEmptyShaped { - CAShapeLayer* mask = (CAShapeLayer*)[[self contentView] layer].mask; - return ([mask isEmptyShaped]); + return (self.shapeData.length == sizeof(CGRectZero) && !memcmp(self.shapeData.bytes, &CGRectZero, sizeof(CGRectZero))); }
- (BOOL) canProvideSnapshot @@ -2653,8 +2707,6 @@ - (void) setRetinaMode:(int)mode
[transform scaleBy:scale];
- [[self contentView] layer].mask.contentsScale = mode ? 2.0 : 1.0; - for (WineBaseView* subview in [self.contentView subviews]) { if ([subview isKindOfClass:[WineBaseView class]]) @@ -3260,6 +3312,7 @@ void macdrv_destroy_cocoa_window(macdrv_window w) WineWindow* window = (WineWindow*)w;
OnMainThread(^{ + window.surface = nil; window.closing = TRUE; [window doOrderOut]; [window close]; @@ -3426,14 +3479,13 @@ void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent) /*********************************************************************** * macdrv_set_window_surface */ -void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex) +void macdrv_set_window_surface(macdrv_window w, void *surface) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w;
OnMainThread(^{ window.surface = surface; - window.surface_mutex = mutex; });
[pool release]; @@ -3471,19 +3523,15 @@ void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count) OnMainThread(^{ if (!rects || !count) { - [window setShape:NULL]; - [window checkEmptyShaped]; + window.shapeData = nil; } else { - CGMutablePathRef path; - unsigned int i; - - path = CGPathCreateMutable(); - for (i = 0; i < count; i++) - CGPathAddRect(path, NULL, cgrect_mac_from_win(rects[i])); - [window setShape:path]; - CGPathRelease(path); + size_t length = sizeof(*rects) * count; + if (window.shapeData.length != length || memcmp(window.shapeData.bytes, rects, length)) + { + window.shapeData = [NSData dataWithBytes:rects length:length]; + } } });
@@ -3506,8 +3554,8 @@ void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha) /*********************************************************************** * macdrv_set_window_color_key */ -void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen, - CGFloat keyBlue) +void macdrv_set_window_color_key(macdrv_window w, uint8_t keyRed, uint8_t keyGreen, + uint8_t keyBlue) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; @@ -3604,9 +3652,7 @@ macdrv_view macdrv_create_view(CGRect rect)
view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(cgrect_mac_from_win(rect))]; [view setWantsLayer:YES]; - [view layer].minificationFilter = retina_on ? kCAFilterLinear : kCAFilterNearest; - [view layer].magnificationFilter = retina_on ? kCAFilterLinear : kCAFilterNearest; - [view layer].contentsScale = retina_on ? 2.0 : 1.0; + [view.layer setDrawsAsynchronously:YES]; [view setAutoresizesSubviews:NO]; [view setAutoresizingMask:NSViewNotSizable]; [view setHidden:YES]; diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h index 3eecb26a01e..7b410fbdef0 100644 --- a/dlls/winemac.drv/macdrv.h +++ b/dlls/winemac.drv/macdrv.h @@ -209,7 +209,6 @@ extern DWORD CDECL macdrv_MsgWaitForMultipleObjectsEx(DWORD count, const HANDLE extern void activate_on_following_focus(void) DECLSPEC_HIDDEN; extern struct window_surface *create_surface(macdrv_window window, const RECT *rect, struct window_surface *old_surface, BOOL use_alpha) DECLSPEC_HIDDEN; -extern void set_window_surface(macdrv_window window, struct window_surface *window_surface) DECLSPEC_HIDDEN; extern void set_surface_use_alpha(struct window_surface *window_surface, BOOL use_alpha) DECLSPEC_HIDDEN; extern void surface_clip_to_visible_rect(struct window_surface *window_surface, const RECT *visible_rect) DECLSPEC_HIDDEN;
diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h index c7f87888fdc..34769771fa7 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -580,15 +580,16 @@ extern void macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev, extern void macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame) DECLSPEC_HIDDEN; extern void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame) DECLSPEC_HIDDEN; extern void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent) DECLSPEC_HIDDEN; -extern void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex) DECLSPEC_HIDDEN; -extern CGImageRef create_surface_image(void *window_surface, CGRect *rect, int copy_data, int color_keyed, - CGFloat key_red, CGFloat key_green, CGFloat key_blue) DECLSPEC_HIDDEN; -extern int get_surface_blit_rects(void *window_surface, const CGRect **rects, int *count) DECLSPEC_HIDDEN; +extern void macdrv_set_window_surface(macdrv_window w, void *surface) DECLSPEC_HIDDEN; +extern CGImageRef create_surface_image(void *window_surface, CGRect *dirty_area, int color_keyed, + uint8_t key_red, uint8_t key_green, uint8_t key_blue) DECLSPEC_HIDDEN; +extern int surface_hit_test(void *window_surface, CGPoint point, int color_keyed, + uint8_t key_red, uint8_t key_green, uint8_t key_blue) DECLSPEC_HIDDEN; extern void macdrv_window_needs_display(macdrv_window w, CGRect rect) DECLSPEC_HIDDEN; extern void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count) DECLSPEC_HIDDEN; extern void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha) DECLSPEC_HIDDEN; -extern void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen, - CGFloat keyBlue) DECLSPEC_HIDDEN; +extern void macdrv_set_window_color_key(macdrv_window w, uint8_t keyRed, uint8_t keyGreen, + uint8_t keyBlue) DECLSPEC_HIDDEN; extern void macdrv_clear_window_color_key(macdrv_window w) DECLSPEC_HIDDEN; extern void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha) DECLSPEC_HIDDEN; extern void macdrv_give_cocoa_window_focus(macdrv_window w, int activate) DECLSPEC_HIDDEN; diff --git a/dlls/winemac.drv/surface.c b/dlls/winemac.drv/surface.c index 6b0abb10780..0eb1f09d417 100644 --- a/dlls/winemac.drv/surface.c +++ b/dlls/winemac.drv/surface.c @@ -64,7 +64,7 @@ struct macdrv_window_surface HRGN region; HRGN drawn; BOOL use_alpha; - RGNDATA *blit_data; + struct shadow_surface *shadow; BYTE *bits; pthread_mutex_t mutex; BITMAPINFO info; /* variable size, must be last */ @@ -72,25 +72,248 @@ struct macdrv_window_surface
static struct macdrv_window_surface *get_mac_surface(struct window_surface *surface);
+/* Shadow surfaces provide a secondary bitmap to enable multithreaded drawing + * without locking the main surface for an extended period of time. + * + * A shadow may contain one or more bitmaps, allocated on demand. Bitmaps are + * kept in a linked list, re-using existing memory whenever possible. This is + * important because paging in large regions of memory can have a latency of + * >20ms, which is not acceptable for drawing. + * + * Unlike macdrv_window_surface, shadow data can be allocated or deallocated + * from a non-Wine thread, so it requires an independent reference counter and + * must not use Windows functions. */ +struct shadow_surface +{ + pthread_mutex_t mutex; + int refcount; + int bitmap_length; /* size of shadow_bitmap in bytes */ + struct shadow_bitmap *bitmaps; /* linked list or NULL */ +}; + +struct shadow_bitmap +{ + union + { + struct + { + struct shadow_surface *parent; + struct shadow_bitmap *next; + CFAllocatorRef cfdata_deallocator; + }; + BYTE pad[32]; /* should be a multiple of 16 */ + }; + BYTE bits[0]; /* variable length */ +}; + +/* Number of shadow bitmaps to keep. Rasterization is asynchronous, so more than + * one bitmap may be alive at any given time. Usually 2 is sufficient. This is + * a failsafe to ensure the list can't grow indefinitely. */ +#define SHADOW_BITMAP_MAX 3 + +static struct shadow_bitmap *shadow_bitmap_free(struct shadow_bitmap *bitmap, + int bitmap_length); +static void shadow_cfdata_dealloc(void *ptr, void *info); + /*********************************************************************** - * update_blit_data + * shadow_surface_create + * + * Creates a new shadow surface. Its refcount is initially set to 1. + * Returns NULL on failure. */ -static void update_blit_data(struct macdrv_window_surface *surface) +static struct shadow_surface *shadow_surface_create(struct macdrv_window_surface* parent) { - HeapFree(GetProcessHeap(), 0, surface->blit_data); - surface->blit_data = NULL; + /* Note: actual bitmap length is rounded up to the nearest pagesize */ + int bitmap_length = sizeof(struct shadow_bitmap) + + parent->info.bmiHeader.biSizeImage; + + /* Although this function can only be called from a Windows thread, the + * deallocator can be called from a macOS thread, so we must use calloc + * instead of HeapAlloc. */ + struct shadow_surface *shadow = calloc(1, sizeof(struct shadow_surface)); + if (!shadow) + return NULL;
- if (surface->drawn) + if (pthread_mutex_init(&shadow->mutex, NULL) != 0) + { + free(shadow); + return NULL; + } + + shadow->refcount = 1; + shadow->bitmap_length = bitmap_length; + return shadow; +} + +/*********************************************************************** + * shadow_surface_release + * + * Decrements shadow surface's refcount. If it becomes zero, the shadow + * is freed along with all of its bitmaps. + * + * shadow->mutex must be held before calling this function. The mutex + * will be released on exit and you should not attempt to access the + * surface again. + * + * IMPORTANT: This function is called from non-Wine threads, so it + * must not use Win32 or Wine functions, including debug + * logging. + */ +static void shadow_surface_release(struct shadow_surface *shadow) +{ + int bitmap_length = shadow->bitmap_length; + int refcount = --shadow->refcount; + struct shadow_bitmap *bitmap = shadow->bitmaps; + + pthread_mutex_unlock(&shadow->mutex); + + if (refcount != 0) + return; + + pthread_mutex_destroy(&shadow->mutex); + free(shadow); + while (bitmap) + bitmap = shadow_bitmap_free(bitmap, bitmap_length); +} + +/*********************************************************************** + * shadow_bitmap_take + * + * Obtains a shadow bitmap from the given shadow surface. Returns the + * bitmap on success or NULL on failure. + * + * On success, shadow->refcount has been incremented. The call must be + * balanced with a call to shadow_bitmap_return, usually accomplished by + * passing bitmap->cfdata_deallocator to CFDataCreateWithBytesNoCopy as + * bytesDeallocator. + * + * IMPORTANT: This function is called from non-Wine threads, so it + * must not use Win32 or Wine functions, including debug + * logging. + */ +static struct shadow_bitmap *shadow_bitmap_take(struct shadow_surface *shadow) +{ + int bitmap_length; + struct shadow_bitmap *bitmap; + CFAllocatorContext cfa_context = { + .version = 0, .info = NULL, + .retain = NULL, .release = NULL, .copyDescription = NULL, + .allocate = NULL, .reallocate = NULL, + .deallocate = shadow_cfdata_dealloc, .preferredSize = NULL + }; + + pthread_mutex_lock(&shadow->mutex); + shadow->refcount++; + + bitmap_length = shadow->bitmap_length; + bitmap = shadow->bitmaps; + if (bitmap) { - HRGN blit = CreateRectRgn(0, 0, 0, 0); + /* Got an existing bitmap, remove it from the linked list */ + shadow->bitmaps = bitmap->next; + bitmap->next = NULL; + } + + pthread_mutex_unlock(&shadow->mutex); + + if (bitmap) + return bitmap; + + /* Nothing was available, make a new bitmap. Once again we cannot use + * Windows functions. mmap is most appropriate due to its low CPU overhead + * and the fact that bitmaps are usually large. Pass shadow as a hint so the + * bitmap will be allocated in a similar memory region. */ + bitmap = mmap(shadow, bitmap_length, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + if (bitmap == MAP_FAILED) + goto failed; + + cfa_context.info = bitmap; + bitmap->cfdata_deallocator = CFAllocatorCreate(NULL, &cfa_context); + if (!bitmap->cfdata_deallocator) + goto failed_unmap; + + bitmap->parent = shadow; + return bitmap; + +failed_unmap: + munmap(bitmap, bitmap_length); +failed: + /* Balance refcount++ since shadow_bitmap_return won't be called */ + pthread_mutex_lock(&shadow->mutex); + shadow_surface_release(shadow); + return NULL; +} + +/*********************************************************************** + * shadow_bitmap_return + * + * Returns a shadow bitmap to its linked list and decrements the + * refcount in the corresponding shadow_surface. + * + * IMPORTANT: This function is called from non-Wine threads, so it + * must not use Win32 or Wine functions, including debug + * logging. + */ +static void shadow_bitmap_return(struct shadow_bitmap *bitmap) +{ + struct shadow_surface *shadow = bitmap->parent; + struct shadow_bitmap **tail = &shadow->bitmaps; + int bitmap_length; + int num_bitmaps = 0;
- if (CombineRgn(blit, surface->drawn, 0, RGN_COPY) > NULLREGION && - (!surface->region || CombineRgn(blit, blit, surface->region, RGN_AND) > NULLREGION) && - OffsetRgn(blit, surface->header.rect.left, surface->header.rect.top) > NULLREGION) - surface->blit_data = get_region_data(blit, 0); + pthread_mutex_lock(&shadow->mutex); + bitmap_length = shadow->bitmap_length;
- DeleteObject(blit); + /* Append the bitmap to the end of the list */ + while (*tail) + { + num_bitmaps++; + tail = &(*tail)->next; } + + /* Refuse to return this bitmap if there are already too many. Free it + * instead. */ + if (num_bitmaps >= SHADOW_BITMAP_MAX) + shadow_bitmap_free(bitmap, bitmap_length); + else + *tail = bitmap; + + shadow_surface_release(shadow); +} + +/*********************************************************************** + * shadow_bitmap_free + * + * Deletes a shadow bitmap. Returns bitmap->next. + * + * IMPORTANT: This function is called from non-Wine threads, so it + * must not use Win32 or Wine functions, including debug + * logging. + */ +static struct shadow_bitmap *shadow_bitmap_free(struct shadow_bitmap *bitmap, + int bitmap_length) +{ + struct shadow_bitmap *next = bitmap->next; + + CFRelease(bitmap->cfdata_deallocator); + munmap(bitmap, bitmap_length); + + return next; +} + +/*********************************************************************** + * shadow_cfdata_dealloc + * + * Wraps shadow_bitmap_return for CFAllocator. + * + * IMPORTANT: This function is called from non-Wine threads, so it + * must not use Win32 or Wine functions, including debug + * logging. + */ +static void shadow_cfdata_dealloc(void *ptr, void *info) +{ + shadow_bitmap_return(info); }
/*********************************************************************** @@ -156,7 +379,6 @@ static void macdrv_surface_set_region(struct window_surface *window_surface, HRG if (surface->region) DeleteObject(surface->region); surface->region = 0; } - update_blit_data(surface);
window_surface->funcs->unlock(window_surface); } @@ -188,7 +410,6 @@ static void macdrv_surface_flush(struct window_surface *window_surface) else surface->drawn = region; } - update_blit_data(surface); reset_bounds(&surface->bounds);
window_surface->funcs->unlock(window_surface); @@ -207,8 +428,14 @@ static void macdrv_surface_destroy(struct window_surface *window_surface) TRACE("freeing %p bits %p\n", surface, surface->bits); if (surface->region) DeleteObject(surface->region); if (surface->drawn) DeleteObject(surface->drawn); - HeapFree(GetProcessHeap(), 0, surface->blit_data); - HeapFree(GetProcessHeap(), 0, surface->bits); + if (surface->shadow) + { + pthread_mutex_lock(&surface->shadow->mutex); + shadow_surface_release(surface->shadow); + } + if (surface->bits && surface->bits != MAP_FAILED) + munmap(surface->bits, surface->info.bmiHeader.biSizeImage); + pthread_mutex_destroy(&surface->mutex); HeapFree(GetProcessHeap(), 0, surface); } @@ -264,7 +491,7 @@ struct window_surface *create_surface(macdrv_window window, const RECT *rect,
surface->info.bmiHeader.biSize = sizeof(surface->info.bmiHeader); surface->info.bmiHeader.biWidth = width; - surface->info.bmiHeader.biHeight = -height; /* top-down */ + surface->info.bmiHeader.biHeight = height; /* bottom-up */ surface->info.bmiHeader.biPlanes = 1; surface->info.bmiHeader.biBitCount = 32; surface->info.bmiHeader.biSizeImage = get_dib_image_size(&surface->info); @@ -291,15 +518,20 @@ struct window_surface *create_surface(macdrv_window window, const RECT *rect, surface->drawn = 0; } } - update_blit_data(surface); surface->use_alpha = use_alpha; - surface->bits = HeapAlloc(GetProcessHeap(), 0, surface->info.bmiHeader.biSizeImage); - if (!surface->bits) goto failed; + surface->shadow = shadow_surface_create(surface); + if (!surface->shadow) goto failed; + /* Map bitmap close to the shadow */ + surface->bits = mmap(surface->shadow, surface->info.bmiHeader.biSizeImage, + PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + if (surface->bits == MAP_FAILED) goto failed; window_background = macdrv_window_background_color(); memset_pattern4(surface->bits, &window_background, surface->info.bmiHeader.biSizeImage);
- TRACE("created %p for %p %s bits %p-%p\n", surface, window, wine_dbgstr_rect(rect), - surface->bits, surface->bits + surface->info.bmiHeader.biSizeImage); + TRACE("created %p for %p %s bits %p-%p shadow %p\n", + surface, window, wine_dbgstr_rect(rect), + surface->bits, surface->bits + surface->info.bmiHeader.biSizeImage, + surface->shadow);
return &surface->header;
@@ -318,121 +550,141 @@ void set_surface_use_alpha(struct window_surface *window_surface, BOOL use_alpha }
/*********************************************************************** - * set_window_surface - */ -void set_window_surface(macdrv_window window, struct window_surface *window_surface) -{ - struct macdrv_window_surface *surface = get_mac_surface(window_surface); - macdrv_set_window_surface(window, window_surface, surface ? &surface->mutex : NULL); -} - -/*********************************************************************** - * get_surface_blit_rects + * create_surface_image * - * Caller must hold the surface lock. Indirectly returns the surface - * blit region rects. Returns zero if the surface has nothing to blit; - * returns non-zero if the surface does have rects to blit (drawn area - * which isn't clipped away by a surface region). + * Creates a CGImageRef from the given surface. Returns NULL if there is + * nothing to draw. On input, *dirty_area is the requested image rect, + * relative to the window whole_rect, a.k.a. visible_rect. On output, + * it's been intersected with that part backed by the surface and is the + * actual size of the returned image. * * IMPORTANT: This function is called from non-Wine threads, so it * must not use Win32 or Wine functions, including debug * logging. */ -int get_surface_blit_rects(void *window_surface, const CGRect **rects, int *count) +CGImageRef create_surface_image(void *window_surface, CGRect *dirty_area, int color_keyed, + uint8_t key_red, uint8_t key_green, uint8_t key_blue) { + static CGColorSpaceRef colorspace = NULL; + CGImageAlphaInfo alpha_info = kCGImageAlphaNoneSkipFirst; + CGImageRef cgimage; + CFDataRef data; + CGDataProviderRef provider; + struct macdrv_window_surface *surface = get_mac_surface(window_surface); + struct shadow_bitmap *shadow;
- if (rects && count) - { - if (surface->blit_data) - { - *rects = (const CGRect*)surface->blit_data->Buffer; - *count = surface->blit_data->rdh.nCount; - } - else - { - *rects = NULL; - *count = 0; - } - } + int surface_width, bytes_per_row, bitmap_length; + int dirty_width, dirty_height, offset;
- return (surface->blit_data != NULL && surface->blit_data->rdh.nCount > 0); -} + if (!surface) + return NULL;
-/*********************************************************************** - * create_surface_image - * - * Caller must hold the surface lock. On input, *rect is the requested - * image rect, relative to the window whole_rect, a.k.a. visible_rect. - * On output, it's been intersected with that part backed by the surface - * and is the actual size of the returned image. copy_data indicates if - * the caller will keep the returned image beyond the point where the - * surface bits can be guaranteed to remain valid and unchanged. If so, - * the bits are copied instead of merely referenced by the image. - * - * IMPORTANT: This function is called from non-Wine threads, so it - * must not use Win32 or Wine functions, including debug - * logging. - */ -CGImageRef create_surface_image(void *window_surface, CGRect *rect, int copy_data, int color_keyed, - CGFloat key_red, CGFloat key_green, CGFloat key_blue) -{ - CGImageRef cgimage = NULL; - struct macdrv_window_surface *surface = get_mac_surface(window_surface); - int width, height; + if (!colorspace) + colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
- width = surface->header.rect.right - surface->header.rect.left; - height = surface->header.rect.bottom - surface->header.rect.top; - *rect = CGRectIntersection(cgrect_from_rect(surface->header.rect), *rect); - if (!CGRectIsEmpty(*rect)) - { - CGRect visrect; - CGColorSpaceRef colorspace; - CGDataProviderRef provider; - int bytes_per_row, offset, size; - CGImageAlphaInfo alphaInfo; + pthread_mutex_lock(&surface->mutex); + surface_width = surface->header.rect.right - surface->header.rect.left; + bytes_per_row = get_dib_stride(surface_width, 32); + bitmap_length = surface->info.bmiHeader.biSizeImage; + if (surface->use_alpha) + alpha_info = kCGImageAlphaPremultipliedFirst;
- visrect = CGRectOffset(*rect, -surface->header.rect.left, -surface->header.rect.top); + /* Clip to bitmap area */ + *dirty_area = CGRectIntersection(cgrect_from_rect(surface->header.rect), + CGRectIntegral(*dirty_area));
- colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); - bytes_per_row = get_dib_stride(width, 32); - offset = CGRectGetMinX(visrect) * 4 + CGRectGetMinY(visrect) * bytes_per_row; - size = min(CGRectGetHeight(visrect) * bytes_per_row, - surface->info.bmiHeader.biSizeImage - offset); + dirty_width = CGRectGetWidth(*dirty_area); + dirty_height = CGRectGetHeight(*dirty_area); + + if (dirty_width <= 0 || dirty_height <= 0) + goto failed; + + shadow = shadow_bitmap_take(surface->shadow); + if (!shadow) + goto failed;
- if (copy_data) + /* Find location from which to read data */ + offset = (CGRectGetMinX(*dirty_area) - surface->header.rect.left) * 4 + + (surface->header.rect.bottom - CGRectGetMaxY(*dirty_area)) + * bytes_per_row; + + if (!color_keyed) + { + /* Copy pixels to shadow buffer. If the width is close or equal to the + * whole bitmap (<= 32 bytes), it's faster to copy one large region. */ + if (dirty_width >= surface_width - 8) { - CFDataRef data = CFDataCreate(NULL, (UInt8*)surface->bits + offset, size); - provider = CGDataProviderCreateWithCFData(data); - CFRelease(data); + /* Align to 16-byte boundaries to get the best memcpy performance */ + int align_offset = offset & ~15; + int align_size = ((offset + (dirty_height - 1) * bytes_per_row + + dirty_width * 4 + 15) & ~15) - align_offset; + memcpy(shadow->bits + align_offset, + surface->bits + align_offset, + align_size); } else - provider = CGDataProviderCreateWithData(NULL, surface->bits + offset, size, NULL); - - alphaInfo = surface->use_alpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; - cgimage = CGImageCreate(CGRectGetWidth(visrect), CGRectGetHeight(visrect), - 8, 32, bytes_per_row, colorspace, - alphaInfo | kCGBitmapByteOrder32Little, - provider, NULL, retina_on, kCGRenderingIntentDefault); - CGDataProviderRelease(provider); - CGColorSpaceRelease(colorspace); + { + /* Copying a smaller width of the bitmap, go line by line */ + int y; + for (y = 0; y < dirty_height; y++) + { + int line_offset = offset + y * bytes_per_row; + int align_offset = line_offset & ~15; + int align_size = ((line_offset + dirty_width * 4 + 15) & ~15) + - align_offset; + memcpy(shadow->bits + align_offset, + surface->bits + align_offset, + align_size); + } + } + } + else + { + /* If using a color key, convert those pixels to transparent. This is + * the best time to do it since we have to copy the data anyway. */ + DWORD key = (key_red << 16) | (key_green << 8) | (key_blue); + int x, y;
- if (color_keyed) + for (y = 0; y < dirty_height; y++) { - CGImageRef maskedImage; - CGFloat components[] = { key_red - 0.5, key_red + 0.5, - key_green - 0.5, key_green + 0.5, - key_blue - 0.5, key_blue + 0.5 }; - maskedImage = CGImageCreateWithMaskingColors(cgimage, components); - if (maskedImage) + for (x = 0; x < dirty_width; x++) { - CGImageRelease(cgimage); - cgimage = maskedImage; + DWORD src = *(DWORD*)(surface->bits + offset + + x * 4 + y * bytes_per_row); + DWORD* dst = (DWORD*)(shadow->bits + offset + + x * 4 + y * bytes_per_row); + if ((src & 0x00FFFFFF) == key) + *dst = 0; + else if (!surface->use_alpha) + /* dst must have alpha channel even if src does not */ + *dst = src | 0xFF000000; + else + *dst = src; } } + + alpha_info = kCGImageAlphaPremultipliedFirst; }
+ /* Safe to unlock surface now */ + pthread_mutex_unlock(&surface->mutex); + + data = CFDataCreateWithBytesNoCopy(NULL, shadow->bits + offset, + bitmap_length - offset, shadow->cfdata_deallocator); + provider = CGDataProviderCreateWithCFData(data); + CFRelease(data); + + cgimage = CGImageCreate(dirty_width, dirty_height, 8, 32, bytes_per_row, + colorspace, alpha_info | kCGBitmapByteOrder32Little, + provider, NULL, FALSE, kCGRenderingIntentDefault); + CFRelease(provider); + return cgimage; + +failed: + pthread_mutex_unlock(&surface->mutex); + return NULL; }
/*********************************************************************** @@ -460,10 +712,59 @@ void surface_clip_to_visible_rect(struct window_surface *window_surface, const R { CombineRgn(surface->drawn, surface->drawn, region, RGN_AND); DeleteObject(region); - - update_blit_data(surface); } }
window_surface->funcs->unlock(window_surface); } + +/*********************************************************************** + * surface_hit_test + * + * Performs a per-pixel hit test on the given surface. Returns FALSE if + * the chosen pixel is transparent or keyed out, TRUE if the pixel is + * clickable. + * + * IMPORTANT: This function is called from non-Wine threads, so it + * must not use Win32 or Wine functions, including debug + * logging. + */ +int surface_hit_test(void *window_surface, CGPoint point, int color_keyed, + uint8_t key_red, uint8_t key_green, uint8_t key_blue) +{ + struct macdrv_window_surface *surface = get_mac_surface(window_surface); + DWORD key = (key_red << 16) | (key_green << 8) | (key_blue); + DWORD pixel; + int surface_width, bytes_per_row; + /* Note: coordinates can be non-integers. Truncate. */ + int point_x = point.x, point_y = point.y; + int retval = TRUE; + + if (!surface) + return TRUE; + + pthread_mutex_lock(&surface->mutex); + surface_width = surface->header.rect.right - surface->header.rect.left; + bytes_per_row = get_dib_stride(surface_width, 32); + + if (!surface->use_alpha && !color_keyed) + /* Opaque surface always succeeds */ + goto done; + + if (point_x < surface->header.rect.left || point_x >= surface->header.rect.right + || point_y < surface->header.rect.top || point_y >= surface->header.rect.bottom) + { + retval = FALSE; + goto done; + } + + pixel = *(DWORD*)(surface->bits + (point_x - surface->header.rect.left) * 4 + + (surface->header.rect.bottom - point_y) * bytes_per_row); + + retval = !((color_keyed && (pixel & 0x00FFFFFF) == key) + || (surface->use_alpha && (pixel & 0xFF000000) == 0)); + +done: + pthread_mutex_unlock(&surface->mutex); + return retval; +} diff --git a/dlls/winemac.drv/window.c b/dlls/winemac.drv/window.c index 9177f493a5f..dba87df37de 100644 --- a/dlls/winemac.drv/window.c +++ b/dlls/winemac.drv/window.c @@ -1899,7 +1899,7 @@ BOOL CDECL macdrv_UpdateLayeredWindow(HWND hwnd, const UPDATELAYEREDWINDOWINFO * if (!surface || !EqualRect(&surface->rect, &rect)) { data->surface = create_surface(data->cocoa_window, &rect, NULL, TRUE); - set_window_surface(data->cocoa_window, data->surface); + macdrv_set_window_surface(data->cocoa_window, data->surface); if (surface) window_surface_release(surface); surface = data->surface; if (data->unminimized_surface) @@ -2139,7 +2139,7 @@ void CDECL macdrv_WindowPosChanged(HWND hwnd, HWND insert_after, UINT swp_flags, } else { - set_window_surface(data->cocoa_window, surface); + macdrv_set_window_surface(data->cocoa_window, surface); if (data->unminimized_surface) { window_surface_release(data->unminimized_surface);
On Tue, Mar 22, 2022 at 07:31:49PM -0600, Elaine Lefler wrote:
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@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
Hi Elaine,
Thanks for the patches. Unfortunately at the moment this patch is too large to review properly. Could you try breaking it down into several smaller, self-contained patches?
Huw.
On Wed, Mar 23, 2022 at 3:42 AM Huw Davies huw@codeweavers.com wrote:
Hi Elaine,
Thanks for the patches. Unfortunately at the moment this patch is too large to review properly. Could you try breaking it down into several smaller, self-contained patches?
Huw.
Hi Huw,
I can try. Unfortunately these changes are very co-dependent, so it's difficult to break them up in any logical way.
If you're okay with reviewing patches that aren't meaningful on their own, I could split it into the following: 1) Move/rename wintab definitions to reduce code duplication (large patch, but it's just a trivial refactor) 2) Correct wintab32.dll behavior (smaller patch) 3) Add macdrv wintab implementation (still around 1000 lines, there's absolutely nothing I can do about this)
Does that work for you? I certainly understand your hesitance to review a large patch from a new developer. Since it's a brand new driver the majority of the code is "all or nothing" because an incomplete implementation doesn't work at all. Best I can do is to separate the new code from the refactors. If you're okay with the above plan, I'll resubmit tomorrow.
Thanks, - Elaine
On Wed, Mar 23, 2022 at 09:46:45PM -0600, Elaine Lefler wrote:
On Wed, Mar 23, 2022 at 3:42 AM Huw Davies huw@codeweavers.com wrote:
Hi Elaine,
Thanks for the patches. Unfortunately at the moment this patch is too large to review properly. Could you try breaking it down into several smaller, self-contained patches?
Huw.
Hi Huw,
I can try. Unfortunately these changes are very co-dependent, so it's difficult to break them up in any logical way.
If you're okay with reviewing patches that aren't meaningful on their own, I could split it into the following:
- Move/rename wintab definitions to reduce code duplication (large
patch, but it's just a trivial refactor)
Yes, this is fine and even encouraged if it reduces the real changes in the rest of the patches. Looking at these bits of your patch, you could probably even split the refactoring into a few patches.
The key point to bear in mind is to keep things simple for the reviewer, so small, bit-sized patches are strongly preferred.
- Correct wintab32.dll behavior (smaller patch)
Again, ideal for a separate patch.
- Add macdrv wintab implementation (still around 1000 lines, there's
absolutely nothing I can do about this)
Note, it doesn't have to work straight away, so you can split it in developmental increments. One point though is that you should try to avoid adding code that isn't called (dead code).
Huw.