Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/intern
diff options
context:
space:
mode:
authorNicholas Rishel <rishel.nick@gmail.com>2021-03-05 02:48:48 +0300
committerNicholas Rishel <rishel.nick@gmail.com>2021-06-22 00:38:51 +0300
commit6f158f834dcfa638639391f37afcb2ca8457cb45 (patch)
tree111b72dc28a7dd9fc9e5d6c6ba9d6be2d3cfd9c8 /intern
parent445d506ac9b59c4eb9de8a475fd89ba908ecbcdf (diff)
Refactor of Wintab to use Wintab supplied mouse movement once verified against system input.
Reviewed By: brecht, LazyDodo Maniphest Tasks: T88852 Differential Revision: https://developer.blender.org/D11508
Diffstat (limited to 'intern')
-rw-r--r--intern/ghost/CMakeLists.txt2
-rw-r--r--intern/ghost/GHOST_Types.h4
-rw-r--r--intern/ghost/intern/GHOST_System.h2
-rw-r--r--intern/ghost/intern/GHOST_SystemWin32.cpp412
-rw-r--r--intern/ghost/intern/GHOST_SystemWin32.h23
-rw-r--r--intern/ghost/intern/GHOST_WindowWin32.cpp255
-rw-r--r--intern/ghost/intern/GHOST_WindowWin32.h87
-rw-r--r--intern/ghost/intern/GHOST_Wintab.cpp491
-rw-r--r--intern/ghost/intern/GHOST_Wintab.h250
9 files changed, 1201 insertions, 325 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt
index 1b5cdb3cda0..40d78a22e6f 100644
--- a/intern/ghost/CMakeLists.txt
+++ b/intern/ghost/CMakeLists.txt
@@ -370,6 +370,7 @@ elseif(WIN32)
intern/GHOST_DropTargetWin32.cpp
intern/GHOST_SystemWin32.cpp
intern/GHOST_WindowWin32.cpp
+ intern/GHOST_Wintab.cpp
intern/GHOST_ContextD3D.h
intern/GHOST_DisplayManagerWin32.h
@@ -377,6 +378,7 @@ elseif(WIN32)
intern/GHOST_SystemWin32.h
intern/GHOST_TaskbarWin32.h
intern/GHOST_WindowWin32.h
+ intern/GHOST_Wintab.h
)
if(NOT WITH_GL_EGL)
diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h
index 3a8d0fbfecf..f18e6f03ede 100644
--- a/intern/ghost/GHOST_Types.h
+++ b/intern/ghost/GHOST_Types.h
@@ -105,7 +105,9 @@ typedef enum {
typedef enum {
GHOST_kTabletAutomatic = 0,
- GHOST_kTabletNative,
+ /* Show as Windows Ink to users to match "Use Windows Ink" in tablet utilities, but we use the
+ dependent Windows Pointer API. */
+ GHOST_kTabletWinPointer,
GHOST_kTabletWintab,
} GHOST_TTabletAPI;
diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h
index 2a7123b293e..9915520691f 100644
--- a/intern/ghost/intern/GHOST_System.h
+++ b/intern/ghost/intern/GHOST_System.h
@@ -239,7 +239,7 @@ class GHOST_System : public GHOST_ISystem {
* Set which tablet API to use. Only affects Windows, other platforms have a single API.
* \param api: Enum indicating which API to use.
*/
- void setTabletAPI(GHOST_TTabletAPI api);
+ virtual void setTabletAPI(GHOST_TTabletAPI api) override;
GHOST_TTabletAPI getTabletAPI(void);
#ifdef WITH_INPUT_NDOF
diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp
index 45b9e88f884..09cfa30eca5 100644
--- a/intern/ghost/intern/GHOST_SystemWin32.cpp
+++ b/intern/ghost/intern/GHOST_SystemWin32.cpp
@@ -866,15 +866,151 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type,
{
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
- if (type == GHOST_kEventButtonDown) {
- window->updateMouseCapture(MousePressed);
+ GHOST_TabletData td = window->getTabletData();
+
+ /* Move mouse to button event position. */
+ if (window->getTabletData().Active != GHOST_kTabletModeNone) {
+ /* Tablet should be handling inbetween mouse moves, only move to event position. */
+ DWORD msgPos = ::GetMessagePos();
+ int msgPosX = GET_X_LPARAM(msgPos);
+ int msgPosY = GET_Y_LPARAM(msgPos);
+ system->pushEvent(new GHOST_EventCursor(
+ ::GetMessageTime(), GHOST_kEventCursorMove, window, msgPosX, msgPosY, td));
}
- else if (type == GHOST_kEventButtonUp) {
- window->updateMouseCapture(MouseReleased);
+
+ window->updateMouseCapture(type == GHOST_kEventButtonDown ? MousePressed : MouseReleased);
+ return new GHOST_EventButton(system->getMilliSeconds(), type, window, mask, td);
+}
+
+void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window)
+{
+ GHOST_Wintab *wt = window->getWintab();
+ if (!wt) {
+ return;
+ }
+
+ GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
+
+ std::vector<GHOST_WintabInfoWin32> wintabInfo;
+ wt->getInput(wintabInfo);
+
+ /* Wintab provided coordinates are untrusted until a Wintab and Win32 button down event match.
+ * This is checked on every button down event, and revoked if there is a mismatch. This can
+ * happen when Wintab incorrectly scales cursor position or is in mouse mode.
+ *
+ * If Wintab was never trusted while processing this Win32 event, a fallback Ghost cursor move
+ * event is created at the position of the Win32 WT_PACKET event. */
+ bool mouseMoveHandled;
+ bool useWintabPos;
+ mouseMoveHandled = useWintabPos = wt->trustCoordinates();
+
+ for (GHOST_WintabInfoWin32 &info : wintabInfo) {
+ switch (info.type) {
+ case GHOST_kEventCursorMove: {
+ if (!useWintabPos) {
+ continue;
+ }
+
+ wt->mapWintabToSysCoordinates(info.x, info.y, info.x, info.y);
+ system->pushEvent(new GHOST_EventCursor(
+ info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
+
+ break;
+ }
+ case GHOST_kEventButtonDown: {
+ UINT message;
+ switch (info.button) {
+ case GHOST_kButtonMaskLeft:
+ message = WM_LBUTTONDOWN;
+ break;
+ case GHOST_kButtonMaskRight:
+ message = WM_RBUTTONDOWN;
+ break;
+ case GHOST_kButtonMaskMiddle:
+ message = WM_MBUTTONDOWN;
+ break;
+ default:
+ continue;
+ }
+
+ /* Wintab buttons are modal, but the API does not inform us what mode a pressed button is
+ * in. Only issue button events if we can steal an equivalent Win32 button event from the
+ * event queue. */
+ MSG msg;
+ if (PeekMessage(&msg, window->getHWND(), message, message, PM_NOYIELD) &&
+ msg.message != WM_QUIT) {
+
+ /* Test for Win32/Wintab button down match. */
+ useWintabPos = wt->testCoordinates(msg.pt.x, msg.pt.y, info.x, info.y);
+ if (!useWintabPos) {
+ continue;
+ }
+
+ /* Steal the Win32 event which was previously peeked. */
+ PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD);
+
+ /* Move cursor to button location, to prevent incorrect cursor position when
+ * transitioning from unsynchronized Win32 to Wintab cursor control. */
+ wt->mapWintabToSysCoordinates(info.x, info.y, info.x, info.y);
+ system->pushEvent(new GHOST_EventCursor(
+ info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
+
+ window->updateMouseCapture(MousePressed);
+ system->pushEvent(
+ new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
+
+ mouseMoveHandled = true;
+ break;
+ }
+ }
+ case GHOST_kEventButtonUp: {
+ if (!useWintabPos) {
+ continue;
+ }
+
+ UINT message;
+ switch (info.button) {
+ case GHOST_kButtonMaskLeft:
+ message = WM_LBUTTONUP;
+ break;
+ case GHOST_kButtonMaskRight:
+ message = WM_RBUTTONUP;
+ break;
+ case GHOST_kButtonMaskMiddle:
+ message = WM_MBUTTONUP;
+ break;
+ default:
+ continue;
+ }
+
+ /* Wintab buttons are modal, but the API does not inform us what mode a pressed button is
+ * in. Only issue button events if we can steal an equivalent Win32 button event from the
+ * event queue. */
+ MSG msg;
+ if (PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD) &&
+ msg.message != WM_QUIT) {
+
+ window->updateMouseCapture(MouseReleased);
+ system->pushEvent(
+ new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
+ }
+ break;
+ }
+ default:
+ break;
+ }
}
- return new GHOST_EventButton(
- system->getMilliSeconds(), type, window, mask, window->getTabletData());
+ /* Fallback cursor movement if Wintab position were never trusted while processing this event. */
+ if (!mouseMoveHandled) {
+ DWORD pos = GetMessagePos();
+ int x = GET_X_LPARAM(pos);
+ int y = GET_Y_LPARAM(pos);
+
+ /* TODO supply tablet data */
+ system->pushEvent(new GHOST_EventCursor(
+ system->getMilliSeconds(), GHOST_kEventCursorMove, window, x, y, GHOST_TABLET_DATA_NONE));
+ }
}
void GHOST_SystemWin32::processPointerEvent(
@@ -882,7 +1018,7 @@ void GHOST_SystemWin32::processPointerEvent(
{
/* Pointer events might fire when changing windows for a device which is set to use Wintab, even
* when when Wintab is left enabled but set to the bottom of Wintab overlap order. */
- if (!window->useTabletAPI(GHOST_kTabletNative)) {
+ if (!window->usingTabletAPI(GHOST_kTabletWinPointer)) {
return;
}
@@ -893,20 +1029,21 @@ void GHOST_SystemWin32::processPointerEvent(
return;
}
- if (!pointerInfo[0].isPrimary) {
- eventHandled = true;
- return; // For multi-touch displays we ignore these events
- }
-
switch (type) {
- case WM_POINTERENTER:
- window->m_tabletInRange = true;
- system->pushEvent(new GHOST_EventCursor(pointerInfo[0].time,
- GHOST_kEventCursorMove,
- window,
- pointerInfo[0].pixelLocation.x,
- pointerInfo[0].pixelLocation.y,
- pointerInfo[0].tabletData));
+ case WM_POINTERUPDATE:
+ /* Coalesced pointer events are reverse chronological order, reorder chronologically.
+ * Only contiguous move events are coalesced. */
+ for (GHOST_TUns32 i = pointerInfo.size(); i-- > 0;) {
+ system->pushEvent(new GHOST_EventCursor(pointerInfo[i].time,
+ GHOST_kEventCursorMove,
+ window,
+ pointerInfo[i].pixelLocation.x,
+ pointerInfo[i].pixelLocation.y,
+ pointerInfo[i].tabletData));
+ }
+
+ /* Leave event unhandled so that system cursor is moved. */
+
break;
case WM_POINTERDOWN:
/* Move cursor to point of contact because GHOST_EventButton does not include position. */
@@ -922,18 +1059,10 @@ void GHOST_SystemWin32::processPointerEvent(
pointerInfo[0].buttonMask,
pointerInfo[0].tabletData));
window->updateMouseCapture(MousePressed);
- break;
- case WM_POINTERUPDATE:
- /* Coalesced pointer events are reverse chronological order, reorder chronologically.
- * Only contiguous move events are coalesced. */
- for (GHOST_TUns32 i = pointerInfo.size(); i-- > 0;) {
- system->pushEvent(new GHOST_EventCursor(pointerInfo[i].time,
- GHOST_kEventCursorMove,
- window,
- pointerInfo[i].pixelLocation.x,
- pointerInfo[i].pixelLocation.y,
- pointerInfo[i].tabletData));
- }
+
+ /* Mark event handled so that mouse button events are not generated. */
+ eventHandled = true;
+
break;
case WM_POINTERUP:
system->pushEvent(new GHOST_EventButton(pointerInfo[0].time,
@@ -942,16 +1071,14 @@ void GHOST_SystemWin32::processPointerEvent(
pointerInfo[0].buttonMask,
pointerInfo[0].tabletData));
window->updateMouseCapture(MouseReleased);
- break;
- case WM_POINTERLEAVE:
- window->m_tabletInRange = false;
+
+ /* Mark event handled so that mouse button events are not generated. */
+ eventHandled = true;
+
break;
default:
break;
}
-
- eventHandled = true;
- system->setCursorPosition(pointerInfo[0].pixelLocation.x, pointerInfo[0].pixelLocation.y);
}
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
@@ -959,18 +1086,14 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
GHOST_TInt32 x_screen, y_screen;
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
- if (window->m_tabletInRange) {
- if (window->useTabletAPI(GHOST_kTabletNative)) {
- /* Tablet input handled in WM_POINTER* events. WM_MOUSEMOVE events in response to tablet
- * input aren't normally generated when using WM_POINTER events, but manually moving the
- * system cursor as we do in WM_POINTER handling does. */
- return NULL;
- }
+ if (window->getTabletData().Active != GHOST_kTabletModeNone) {
+ /* While pen devices are in range, cursor movement is handled by tablet input processing. */
+ return NULL;
}
system->getCursorPosition(x_screen, y_screen);
- if (window->getCursorGrabModeIsWarp() && !window->m_tabletInRange) {
+ if (window->getCursorGrabModeIsWarp()) {
GHOST_TInt32 x_new = x_screen;
GHOST_TInt32 y_new = y_screen;
GHOST_TInt32 x_accum, y_accum;
@@ -982,7 +1105,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
}
/* Could also clamp to screen bounds wrap with a window outside the view will fail atm.
- * Use offset of 8 in case the window is at screen bounds. */
+ * Use inset in case the window is at screen bounds. */
bounds.wrapPoint(x_new, y_new, 2, window->getCursorGrabAxis());
window->getCursorGrabAccum(x_accum, y_accum);
@@ -998,7 +1121,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
window,
x_screen + x_accum,
y_screen + y_accum,
- window->getTabletData());
+ GHOST_TABLET_DATA_NONE);
}
}
else {
@@ -1007,7 +1130,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
window,
x_screen,
y_screen,
- window->getTabletData());
+ GHOST_TABLET_DATA_NONE);
}
return NULL;
}
@@ -1117,6 +1240,23 @@ GHOST_EventKey *GHOST_SystemWin32::processKeyEvent(GHOST_WindowWin32 *window, RA
return event;
}
+GHOST_Event *GHOST_SystemWin32::processWindowSizeEvent(GHOST_WindowWin32 *window)
+{
+ GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
+ GHOST_Event *sizeEvent = new GHOST_Event(
+ system->getMilliSeconds(), GHOST_kEventWindowSize, window);
+
+ /* We get WM_SIZE before we fully init. Do not dispatch before we are continuously resizing. */
+ if (window->m_inLiveResize) {
+ system->pushEvent(sizeEvent);
+ system->dispatchEvents();
+ return NULL;
+ }
+ else {
+ return sizeEvent;
+ }
+}
+
GHOST_Event *GHOST_SystemWin32::processWindowEvent(GHOST_TEventType type,
GHOST_WindowWin32 *window)
{
@@ -1124,7 +1264,6 @@ GHOST_Event *GHOST_SystemWin32::processWindowEvent(GHOST_TEventType type,
if (type == GHOST_kEventWindowActivate) {
system->getWindowManager()->setActiveWindow(window);
- window->bringTabletContextToFront();
}
return new GHOST_Event(system->getMilliSeconds(), type, window);
@@ -1152,6 +1291,31 @@ GHOST_TSuccess GHOST_SystemWin32::pushDragDropEvent(GHOST_TEventType eventType,
system->getMilliSeconds(), eventType, draggedObjectType, window, mouseX, mouseY, data));
}
+void GHOST_SystemWin32::setTabletAPI(GHOST_TTabletAPI api)
+{
+ GHOST_System::setTabletAPI(api);
+
+ /* If API is set to WinPointer (Windows Ink), unload Wintab so that trouble drivers don't disable
+ * Windows Ink. Load Wintab when API is Automatic because decision logic relies on knowing
+ * whether a Wintab device is present. */
+ const bool loadWintab = GHOST_kTabletWinPointer != api;
+ GHOST_WindowManager *wm = getWindowManager();
+
+ for (GHOST_IWindow *win : wm->getWindows()) {
+ GHOST_WindowWin32 *windowWin32 = (GHOST_WindowWin32 *)win;
+ if (loadWintab) {
+ windowWin32->loadWintab(GHOST_kWindowStateMinimized != windowWin32->getState());
+
+ if (windowWin32->usingTabletAPI(GHOST_kTabletWintab)) {
+ windowWin32->resetPointerPenInfo();
+ }
+ }
+ else {
+ windowWin32->closeWintab();
+ }
+ }
+}
+
void GHOST_SystemWin32::processMinMaxInfo(MINMAXINFO *minmax)
{
minmax->ptMinTrackSize.x = 320;
@@ -1386,33 +1550,100 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
case SC_KEYMENU:
eventHandled = true;
break;
- case SC_RESTORE:
+ case SC_RESTORE: {
::ShowWindow(hwnd, SW_RESTORE);
window->setState(window->getState());
+
+ GHOST_Wintab *wt = window->getWintab();
+ if (wt) {
+ wt->enable();
+ }
+
eventHandled = true;
break;
+ }
+ case SC_MAXIMIZE: {
+ GHOST_Wintab *wt = window->getWintab();
+ if (wt) {
+ wt->enable();
+ }
+ /* Don't report event as handled so that default handling occurs. */
+ break;
+ }
+ case SC_MINIMIZE: {
+ GHOST_Wintab *wt = window->getWintab();
+ if (wt) {
+ wt->disable();
+ }
+ /* Don't report event as handled so that default handling occurs. */
+ break;
+ }
}
break;
////////////////////////////////////////////////////////////////////////
// Wintab events, processed
////////////////////////////////////////////////////////////////////////
- case WT_PACKET:
- window->processWin32TabletEvent(wParam, lParam);
+ case WT_CSRCHANGE: {
+ GHOST_Wintab *wt = window->getWintab();
+ if (wt) {
+ wt->updateCursorInfo();
+ }
+ eventHandled = true;
+ break;
+ }
+ case WT_PROXIMITY: {
+ GHOST_Wintab *wt = window->getWintab();
+ if (wt) {
+ bool inRange = LOWORD(lParam);
+ if (inRange) {
+ /* Some devices don't emit WT_CSRCHANGE events, so update cursor info here. */
+ wt->updateCursorInfo();
+ }
+ else {
+ wt->leaveRange();
+ }
+ }
+ eventHandled = true;
break;
- case WT_CSRCHANGE:
- case WT_PROXIMITY:
- window->processWin32TabletInitEvent();
+ }
+ case WT_INFOCHANGE: {
+ GHOST_Wintab *wt = window->getWintab();
+ if (wt) {
+ wt->processInfoChange(lParam);
+
+ if (window->usingTabletAPI(GHOST_kTabletWintab)) {
+ window->resetPointerPenInfo();
+ }
+ }
+ eventHandled = true;
+ break;
+ }
+ case WT_PACKET:
+ processWintabEvent(window);
+ eventHandled = true;
break;
////////////////////////////////////////////////////////////////////////
// Pointer events, processed
////////////////////////////////////////////////////////////////////////
- case WM_POINTERENTER:
- case WM_POINTERDOWN:
case WM_POINTERUPDATE:
+ case WM_POINTERDOWN:
case WM_POINTERUP:
- case WM_POINTERLEAVE:
processPointerEvent(msg, window, wParam, lParam, eventHandled);
break;
+ case WM_POINTERLEAVE: {
+ GHOST_TUns32 pointerId = GET_POINTERID_WPARAM(wParam);
+ POINTER_INFO pointerInfo;
+ if (!GetPointerInfo(pointerId, &pointerInfo)) {
+ break;
+ }
+
+ /* Reset pointer pen info if pen device has left tracking range. */
+ if (pointerInfo.pointerType == PT_PEN && !IS_POINTER_INRANGE_WPARAM(wParam)) {
+ window->resetPointerPenInfo();
+ eventHandled = true;
+ }
+ break;
+ }
////////////////////////////////////////////////////////////////////////
// Mouse events, processed
////////////////////////////////////////////////////////////////////////
@@ -1451,7 +1682,20 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
}
break;
case WM_MOUSEMOVE:
+ if (!window->m_mousePresent) {
+ TRACKMOUSEEVENT tme = {sizeof(tme)};
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = hwnd;
+ TrackMouseEvent(&tme);
+ window->m_mousePresent = true;
+ GHOST_Wintab *wt = window->getWintab();
+ if (wt) {
+ wt->gainFocus();
+ }
+ }
+
event = processCursorEvent(window);
+
break;
case WM_MOUSEWHEEL: {
/* The WM_MOUSEWHEEL message is sent to the focus window
@@ -1486,7 +1730,17 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
window->loadCursor(true, GHOST_kStandardCursorDefault);
}
break;
-
+ case WM_MOUSELEAVE: {
+ window->m_mousePresent = false;
+ if (window->getTabletData().Active == GHOST_kTabletModeNone) {
+ processCursorEvent(window);
+ }
+ GHOST_Wintab *wt = window->getWintab();
+ if (wt) {
+ wt->loseFocus();
+ }
+ break;
+ }
////////////////////////////////////////////////////////////////////////
// Mouse events, ignored
////////////////////////////////////////////////////////////////////////
@@ -1534,7 +1788,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
* will not be dispatched to OUR active window if we minimize one of OUR windows. */
if (LOWORD(wParam) == WA_INACTIVE)
window->lostMouseCapture();
- window->processWin32TabletActivateEvent(GET_WM_ACTIVATE_STATE(wParam, lParam));
+
lResult = ::DefWindowProc(hwnd, msg, wParam, lParam);
break;
}
@@ -1576,6 +1830,8 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
/* Let DefWindowProc handle it. */
break;
case WM_SIZING:
+ event = processWindowSizeEvent(window);
+ break;
case WM_SIZE:
/* The WM_SIZE message is sent to a window after its size has changed.
* The WM_SIZE and WM_MOVE messages are not sent if an application handles the
@@ -1583,15 +1839,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
* to perform any move or size change processing during the WM_WINDOWPOSCHANGED
* message without calling DefWindowProc.
*/
- /* we get first WM_SIZE before we fully init.
- * So, do not dispatch before we continuously resizing. */
- if (window->m_inLiveResize) {
- system->pushEvent(processWindowEvent(GHOST_kEventWindowSize, window));
- system->dispatchEvents();
- }
- else {
- event = processWindowEvent(GHOST_kEventWindowSize, window);
- }
+ event = processWindowSizeEvent(window);
break;
case WM_CAPTURECHANGED:
window->lostMouseCapture();
@@ -1642,6 +1890,24 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
SWP_NOZORDER | SWP_NOACTIVATE);
}
break;
+ case WM_DISPLAYCHANGE: {
+ GHOST_Wintab *wt = window->getWintab();
+ if (wt) {
+ for (GHOST_IWindow *iter_win : system->getWindowManager()->getWindows()) {
+ GHOST_WindowWin32 *iter_win32win = (GHOST_WindowWin32 *)iter_win;
+ wt->remapCoordinates();
+ }
+ }
+ break;
+ }
+ case WM_KILLFOCUS:
+ /* The WM_KILLFOCUS message is sent to a window immediately before it loses the keyboard
+ * focus. We want to prevent this if a window is still active and it loses focus to
+ * nowhere. */
+ if (!wParam && hwnd == ::GetActiveWindow()) {
+ ::SetFocus(hwnd);
+ }
+ break;
////////////////////////////////////////////////////////////////////////
// Window events, ignored
////////////////////////////////////////////////////////////////////////
@@ -1678,12 +1944,6 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
* object associated with the window.
*/
break;
- case WM_KILLFOCUS:
- /* The WM_KILLFOCUS message is sent to a window immediately before it loses the
- * keyboard focus. We want to prevent this if a window is still active and it loses
- * focus to nowhere. */
- if (!wParam && hwnd == ::GetActiveWindow())
- ::SetFocus(hwnd);
case WM_SHOWWINDOW:
/* The WM_SHOWWINDOW message is sent to a window when the window is
* about to be hidden or shown. */
diff --git a/intern/ghost/intern/GHOST_SystemWin32.h b/intern/ghost/intern/GHOST_SystemWin32.h
index 51c0c984710..7dd61421d4c 100644
--- a/intern/ghost/intern/GHOST_SystemWin32.h
+++ b/intern/ghost/intern/GHOST_SystemWin32.h
@@ -265,6 +265,16 @@ class GHOST_SystemWin32 : public GHOST_System {
int mouseY,
void *data);
+ /***************************************************************************************
+ ** Modify tablet API
+ ***************************************************************************************/
+
+ /**
+ * Set which tablet API to use.
+ * \param api: Enum indicating which API to use.
+ */
+ void setTabletAPI(GHOST_TTabletAPI api) override;
+
protected:
/**
* Initializes the system.
@@ -309,6 +319,12 @@ class GHOST_SystemWin32 : public GHOST_System {
GHOST_TButtonMask mask);
/**
+ * Creates tablet events from Wintab events.
+ * \param window: The window receiving the event (the active window).
+ */
+ static void processWintabEvent(GHOST_WindowWin32 *window);
+
+ /**
* Creates tablet events from pointer events.
* \param type: The type of pointer event.
* \param window: The window receiving the event (the active window).
@@ -352,6 +368,13 @@ class GHOST_SystemWin32 : public GHOST_System {
GHOST_TKey processSpecialKey(short vKey, short scanCode) const;
/**
+ * Creates a window size event.
+ * \param window: The window receiving the event (the active window).
+ * \return The event created.
+ */
+ static GHOST_Event *processWindowSizeEvent(GHOST_WindowWin32 *window);
+
+ /**
* Creates a window event.
* \param type: The type of event to create.
* \param window: The window receiving the event (the active window).
diff --git a/intern/ghost/intern/GHOST_WindowWin32.cpp b/intern/ghost/intern/GHOST_WindowWin32.cpp
index eeafe333633..33c79daf11d 100644
--- a/intern/ghost/intern/GHOST_WindowWin32.cpp
+++ b/intern/ghost/intern/GHOST_WindowWin32.cpp
@@ -21,8 +21,6 @@
* \ingroup GHOST
*/
-#define _USE_MATH_DEFINES
-
#include "GHOST_WindowWin32.h"
#include "GHOST_ContextD3D.h"
#include "GHOST_ContextNone.h"
@@ -72,7 +70,7 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
bool is_debug,
bool dialog)
: GHOST_Window(width, height, state, wantStereoVisual, false),
- m_tabletInRange(false),
+ m_mousePresent(false),
m_inLiveResize(false),
m_system(system),
m_hDC(0),
@@ -82,6 +80,8 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_nPressedButtons(0),
m_customCursor(0),
m_wantAlphaBackground(alphaBackground),
+ m_wintab(NULL),
+ m_lastPointerTabletData(GHOST_TABLET_DATA_NONE),
m_normal_state(GHOST_kWindowStateNormal),
m_user32(NULL),
m_parentWindowHwnd(parentwindow ? parentwindow->m_hWnd : HWND_DESKTOP),
@@ -90,10 +90,6 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
wchar_t *title_16 = alloc_utf16_from_8((char *)title, 0);
RECT win_rect = {left, top, (long)(left + width), (long)(top + height)};
- // Initialize tablet variables
- memset(&m_wintab, 0, sizeof(m_wintab));
- m_tabletData = GHOST_TABLET_DATA_NONE;
-
DWORD style = parentwindow ?
WS_POPUPWINDOW | WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX :
WS_OVERLAPPEDWINDOW;
@@ -218,65 +214,10 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
}
// Initialize Wintab
- m_wintab.handle = ::LoadLibrary("Wintab32.dll");
- if (m_wintab.handle && m_system->getTabletAPI() != GHOST_kTabletNative) {
- // Get API functions
- m_wintab.info = (GHOST_WIN32_WTInfo)::GetProcAddress(m_wintab.handle, "WTInfoA");
- m_wintab.open = (GHOST_WIN32_WTOpen)::GetProcAddress(m_wintab.handle, "WTOpenA");
- m_wintab.close = (GHOST_WIN32_WTClose)::GetProcAddress(m_wintab.handle, "WTClose");
- m_wintab.packet = (GHOST_WIN32_WTPacket)::GetProcAddress(m_wintab.handle, "WTPacket");
- m_wintab.enable = (GHOST_WIN32_WTEnable)::GetProcAddress(m_wintab.handle, "WTEnable");
- m_wintab.overlap = (GHOST_WIN32_WTOverlap)::GetProcAddress(m_wintab.handle, "WTOverlap");
-
- // Let's see if we can initialize tablet here.
- // Check if WinTab available by getting system context info.
- LOGCONTEXT lc = {0};
- lc.lcOptions |= CXO_SYSTEM;
- if (m_wintab.open && m_wintab.info && m_wintab.info(WTI_DEFSYSCTX, 0, &lc)) {
- // Now init the tablet
- /* The maximum tablet size, pressure and orientation (tilt) */
- AXIS TabletX, TabletY, Pressure, Orientation[3];
-
- // Open a Wintab context
-
- // Open the context
- lc.lcPktData = PACKETDATA;
- lc.lcPktMode = PACKETMODE;
- lc.lcOptions |= CXO_MESSAGES;
- lc.lcMoveMask = PACKETDATA;
-
- /* Set the entire tablet as active */
- m_wintab.info(WTI_DEVICES, DVC_X, &TabletX);
- m_wintab.info(WTI_DEVICES, DVC_Y, &TabletY);
-
- /* get the max pressure, to divide into a float */
- BOOL pressureSupport = m_wintab.info(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
- if (pressureSupport)
- m_wintab.maxPressure = Pressure.axMax;
- else
- m_wintab.maxPressure = 0;
-
- /* get the max tilt axes, to divide into floats */
- BOOL tiltSupport = m_wintab.info(WTI_DEVICES, DVC_ORIENTATION, &Orientation);
- if (tiltSupport) {
- /* does the tablet support azimuth ([0]) and altitude ([1]) */
- if (Orientation[0].axResolution && Orientation[1].axResolution) {
- /* all this assumes the minimum is 0 */
- m_wintab.maxAzimuth = Orientation[0].axMax;
- m_wintab.maxAltitude = Orientation[1].axMax;
- }
- else { /* No so don't do tilt stuff. */
- m_wintab.maxAzimuth = m_wintab.maxAltitude = 0;
- }
- }
-
- // The Wintab spec says we must open the context disabled if we are using cursor masks.
- m_wintab.tablet = m_wintab.open(m_hWnd, &lc, FALSE);
- if (m_wintab.enable && m_wintab.tablet) {
- m_wintab.enable(m_wintab.tablet, TRUE);
- }
- }
+ if (system->getTabletAPI() != GHOST_kTabletWinPointer) {
+ loadWintab(GHOST_kWindowStateMinimized != state);
}
+
CoCreateInstance(
CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (LPVOID *)&m_Bar);
}
@@ -289,14 +230,7 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
m_Bar = NULL;
}
- if (m_wintab.handle) {
- if (m_wintab.close && m_wintab.tablet) {
- m_wintab.close(m_wintab.tablet);
- }
-
- FreeLibrary(m_wintab.handle);
- memset(&m_wintab, 0, sizeof(m_wintab));
- }
+ closeWintab();
if (m_user32) {
FreeLibrary(m_user32);
@@ -913,20 +847,16 @@ GHOST_TSuccess GHOST_WindowWin32::hasCursorShape(GHOST_TStandardCursor cursorSha
GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(
std::vector<GHOST_PointerInfoWin32> &outPointerInfo, WPARAM wParam, LPARAM lParam)
{
- if (!useTabletAPI(GHOST_kTabletNative)) {
- return GHOST_kFailure;
- }
-
GHOST_TInt32 pointerId = GET_POINTERID_WPARAM(wParam);
GHOST_TInt32 isPrimary = IS_POINTER_PRIMARY_WPARAM(wParam);
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem();
- GHOST_TUns32 outCount;
+ GHOST_TUns32 outCount = 0;
- if (!(GetPointerInfoHistory(pointerId, &outCount, NULL))) {
+ if (!(GetPointerPenInfoHistory(pointerId, &outCount, NULL))) {
return GHOST_kFailure;
}
- auto pointerPenInfo = std::vector<POINTER_PEN_INFO>(outCount);
+ std::vector<POINTER_PEN_INFO> pointerPenInfo(outCount);
outPointerInfo.resize(outCount);
if (!(GetPointerPenInfoHistory(pointerId, &outCount, pointerPenInfo.data()))) {
@@ -988,148 +918,77 @@ GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(
}
}
+ if (!outPointerInfo.empty()) {
+ m_lastPointerTabletData = outPointerInfo.back().tabletData;
+ }
+
return GHOST_kSuccess;
}
-void GHOST_WindowWin32::processWin32TabletActivateEvent(WORD state)
+void GHOST_WindowWin32::resetPointerPenInfo()
{
- if (!useTabletAPI(GHOST_kTabletWintab)) {
- return;
- }
-
- if (m_wintab.enable && m_wintab.tablet) {
- m_wintab.enable(m_wintab.tablet, state);
-
- if (m_wintab.overlap && state) {
- m_wintab.overlap(m_wintab.tablet, TRUE);
- }
- }
+ m_lastPointerTabletData = GHOST_TABLET_DATA_NONE;
}
-bool GHOST_WindowWin32::useTabletAPI(GHOST_TTabletAPI api) const
+GHOST_Wintab *GHOST_WindowWin32::getWintab() const
{
- if (m_system->getTabletAPI() == api) {
- return true;
- }
- else if (m_system->getTabletAPI() == GHOST_kTabletAutomatic) {
- if (m_wintab.tablet)
- return api == GHOST_kTabletWintab;
- else
- return api == GHOST_kTabletNative;
- }
- else {
- return false;
- }
+ return m_wintab;
}
-void GHOST_WindowWin32::processWin32TabletInitEvent()
+void GHOST_WindowWin32::loadWintab(bool enable)
{
- if (!useTabletAPI(GHOST_kTabletWintab)) {
- return;
- }
-
- // Let's see if we can initialize tablet here
- if (m_wintab.info && m_wintab.tablet) {
- AXIS Pressure, Orientation[3]; /* The maximum tablet size */
-
- BOOL pressureSupport = m_wintab.info(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
- if (pressureSupport)
- m_wintab.maxPressure = Pressure.axMax;
- else
- m_wintab.maxPressure = 0;
-
- BOOL tiltSupport = m_wintab.info(WTI_DEVICES, DVC_ORIENTATION, &Orientation);
- if (tiltSupport) {
- /* does the tablet support azimuth ([0]) and altitude ([1]) */
- if (Orientation[0].axResolution && Orientation[1].axResolution) {
- m_wintab.maxAzimuth = Orientation[0].axMax;
- m_wintab.maxAltitude = Orientation[1].axMax;
- }
- else { /* No so don't do tilt stuff. */
- m_wintab.maxAzimuth = m_wintab.maxAltitude = 0;
+ if (!m_wintab) {
+ if (m_wintab = GHOST_Wintab::loadWintab(m_hWnd)) {
+ if (enable) {
+ m_wintab->enable();
+
+ /* Focus Wintab if cursor is inside this window. This ensures Wintab is enabled when the
+ * tablet is used to change the Tablet API. */
+ GHOST_TInt32 x, y;
+ if (m_system->getCursorPosition(x, y)) {
+ GHOST_Rect rect;
+ getClientBounds(rect);
+
+ if (rect.isInside(x, y)) {
+ m_wintab->gainFocus();
+ }
+ }
}
}
}
+}
- m_tabletData.Active = GHOST_kTabletModeNone;
+void GHOST_WindowWin32::closeWintab()
+{
+ delete m_wintab;
+ m_wintab = NULL;
}
-void GHOST_WindowWin32::processWin32TabletEvent(WPARAM wParam, LPARAM lParam)
+bool GHOST_WindowWin32::usingTabletAPI(GHOST_TTabletAPI api) const
{
- if (!useTabletAPI(GHOST_kTabletWintab)) {
- return;
+ if (m_system->getTabletAPI() == api) {
+ return true;
}
-
- if (m_wintab.packet && m_wintab.tablet) {
- PACKET pkt;
- if (m_wintab.packet((HCTX)lParam, wParam, &pkt)) {
- switch (pkt.pkCursor % 3) { /* % 3 for multiple devices ("DualTrack") */
- case 0:
- m_tabletData.Active = GHOST_kTabletModeNone; /* puck - not yet supported */
- break;
- case 1:
- m_tabletData.Active = GHOST_kTabletModeStylus; /* stylus */
- break;
- case 2:
- m_tabletData.Active = GHOST_kTabletModeEraser; /* eraser */
- break;
- }
-
- if (m_wintab.maxPressure > 0) {
- m_tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_wintab.maxPressure;
- }
- else {
- m_tabletData.Pressure = 1.0f;
- }
-
- if ((m_wintab.maxAzimuth > 0) && (m_wintab.maxAltitude > 0)) {
- ORIENTATION ort = pkt.pkOrientation;
- float vecLen;
- float altRad, azmRad; /* in radians */
-
- /*
- * from the wintab spec:
- * orAzimuth Specifies the clockwise rotation of the
- * cursor about the z axis through a full circular range.
- *
- * orAltitude Specifies the angle with the x-y plane
- * through a signed, semicircular range. Positive values
- * specify an angle upward toward the positive z axis;
- * negative values specify an angle downward toward the negative z axis.
- *
- * wintab.h defines .orAltitude as a UINT but documents .orAltitude
- * as positive for upward angles and negative for downward angles.
- * WACOM uses negative altitude values to show that the pen is inverted;
- * therefore we cast .orAltitude as an (int) and then use the absolute value.
- */
-
- /* convert raw fixed point data to radians */
- altRad = (float)((fabs((float)ort.orAltitude) / (float)m_wintab.maxAltitude) * M_PI / 2.0);
- azmRad = (float)(((float)ort.orAzimuth / (float)m_wintab.maxAzimuth) * M_PI * 2.0);
-
- /* find length of the stylus' projected vector on the XY plane */
- vecLen = cos(altRad);
-
- /* from there calculate X and Y components based on azimuth */
- m_tabletData.Xtilt = sin(azmRad) * vecLen;
- m_tabletData.Ytilt = (float)(sin(M_PI / 2.0 - azmRad) * vecLen);
- }
- else {
- m_tabletData.Xtilt = 0.0f;
- m_tabletData.Ytilt = 0.0f;
- }
+ else if (m_system->getTabletAPI() == GHOST_kTabletAutomatic) {
+ if (m_wintab && m_wintab->devicesPresent()) {
+ return api == GHOST_kTabletWintab;
+ }
+ else {
+ return api == GHOST_kTabletWinPointer;
}
}
+ else {
+ return false;
+ }
}
-void GHOST_WindowWin32::bringTabletContextToFront()
+GHOST_TabletData GHOST_WindowWin32::getTabletData()
{
- if (!useTabletAPI(GHOST_kTabletWintab)) {
- return;
+ if (usingTabletAPI(GHOST_kTabletWintab)) {
+ return m_wintab ? m_wintab->getLastTabletData() : GHOST_TABLET_DATA_NONE;
}
-
- if (m_wintab.overlap && m_wintab.tablet) {
- m_wintab.overlap(m_wintab.tablet, TRUE);
+ else {
+ return m_lastPointerTabletData;
}
}
diff --git a/intern/ghost/intern/GHOST_WindowWin32.h b/intern/ghost/intern/GHOST_WindowWin32.h
index c261dffc578..3c1f32476de 100644
--- a/intern/ghost/intern/GHOST_WindowWin32.h
+++ b/intern/ghost/intern/GHOST_WindowWin32.h
@@ -30,29 +30,16 @@
#include "GHOST_TaskbarWin32.h"
#include "GHOST_Window.h"
+#include "GHOST_Wintab.h"
#ifdef WITH_INPUT_IME
# include "GHOST_ImeWin32.h"
#endif
#include <vector>
-#include <wintab.h>
-// PACKETDATA and PACKETMODE modify structs in pktdef.h, so make sure they come first
-#define PACKETDATA (PK_BUTTONS | PK_NORMAL_PRESSURE | PK_ORIENTATION | PK_CURSOR)
-#define PACKETMODE PK_BUTTONS
-#include <pktdef.h>
-
class GHOST_SystemWin32;
class GHOST_DropTargetWin32;
-// typedefs for WinTab functions to allow dynamic loading
-typedef UINT(API *GHOST_WIN32_WTInfo)(UINT, UINT, LPVOID);
-typedef HCTX(API *GHOST_WIN32_WTOpen)(HWND, LPLOGCONTEXTA, BOOL);
-typedef BOOL(API *GHOST_WIN32_WTClose)(HCTX);
-typedef BOOL(API *GHOST_WIN32_WTPacket)(HCTX, UINT, LPVOID);
-typedef BOOL(API *GHOST_WIN32_WTEnable)(HCTX, BOOL);
-typedef BOOL(API *GHOST_WIN32_WTOverlap)(HCTX, BOOL);
-
// typedefs for user32 functions to allow dynamic loading of Windows 10 DPI scaling functions
typedef UINT(API *GHOST_WIN32_GetDpiForWindow)(HWND);
@@ -62,7 +49,6 @@ struct GHOST_PointerInfoWin32 {
GHOST_TButtonMask buttonMask;
POINT pixelLocation;
GHOST_TUns64 time;
-
GHOST_TabletData tabletData;
};
@@ -256,16 +242,11 @@ class GHOST_WindowWin32 : public GHOST_Window {
HCURSOR getStandardCursor(GHOST_TStandardCursor shape) const;
void loadCursor(bool visible, GHOST_TStandardCursor cursorShape) const;
- const GHOST_TabletData &getTabletData()
- {
- return m_tabletData;
- }
-
/**
* Query whether given tablet API should be used.
* \param api: Tablet API to test.
*/
- bool useTabletAPI(GHOST_TTabletAPI api) const;
+ bool usingTabletAPI(GHOST_TTabletAPI api) const;
/**
* Translate WM_POINTER events into GHOST_PointerInfoWin32 structs.
@@ -278,10 +259,34 @@ class GHOST_WindowWin32 : public GHOST_Window {
WPARAM wParam,
LPARAM lParam);
- void processWin32TabletActivateEvent(WORD state);
- void processWin32TabletInitEvent();
- void processWin32TabletEvent(WPARAM wParam, LPARAM lParam);
- void bringTabletContextToFront();
+ /**
+ * Resets pointer pen tablet state.
+ */
+ void resetPointerPenInfo();
+
+ /**
+ * Retrieves pointer to Wintab if Wintab is the set Tablet API.
+ * \return Pointer to Wintab member.
+ */
+ GHOST_Wintab *getWintab() const;
+
+ /**
+ * Loads Wintab context for the window.
+ * \param enable: True if Wintab should be enabled after loading. Wintab should not be enabled if
+ * the window is minimzed.
+ */
+ void loadWintab(bool enable);
+
+ /**
+ * Closes Wintab for the window.
+ */
+ void closeWintab();
+
+ /**
+ * Get the most recent Windows Pointer tablet data.
+ * \return Most recent pointer tablet data.
+ */
+ GHOST_TabletData getTabletData();
GHOST_TSuccess beginFullScreen() const
{
@@ -295,10 +300,10 @@ class GHOST_WindowWin32 : public GHOST_Window {
GHOST_TUns16 getDPIHint() override;
- /** Whether a tablet stylus is being tracked. */
- bool m_tabletInRange;
+ /** True if the mouse is either over or captured by the window. */
+ bool m_mousePresent;
- /** if the window currently resizing */
+ /** True if the window currently resizing. */
bool m_inLiveResize;
#ifdef WITH_INPUT_IME
@@ -382,27 +387,11 @@ class GHOST_WindowWin32 : public GHOST_Window {
static const wchar_t *s_windowClassName;
static const int s_maxTitleLength;
- /** Tablet data for GHOST */
- GHOST_TabletData m_tabletData;
-
- /* Wintab API */
- struct {
- /** `WinTab.dll` handle. */
- HMODULE handle = NULL;
-
- /** API functions */
- GHOST_WIN32_WTInfo info;
- GHOST_WIN32_WTOpen open;
- GHOST_WIN32_WTClose close;
- GHOST_WIN32_WTPacket packet;
- GHOST_WIN32_WTEnable enable;
- GHOST_WIN32_WTOverlap overlap;
-
- /** Stores the Tablet context if detected Tablet features using `WinTab.dll` */
- HCTX tablet;
- LONG maxPressure;
- LONG maxAzimuth, maxAltitude;
- } m_wintab;
+ /** Pointer to Wintab manager if Wintab is loaded. */
+ GHOST_Wintab *m_wintab;
+
+ /** Most recent tablet data. */
+ GHOST_TabletData m_lastPointerTabletData;
GHOST_TWindowState m_normal_state;
diff --git a/intern/ghost/intern/GHOST_Wintab.cpp b/intern/ghost/intern/GHOST_Wintab.cpp
new file mode 100644
index 00000000000..47ef484145b
--- /dev/null
+++ b/intern/ghost/intern/GHOST_Wintab.cpp
@@ -0,0 +1,491 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup GHOST
+ */
+
+#define _USE_MATH_DEFINES
+
+#include "GHOST_Wintab.h"
+
+GHOST_Wintab *GHOST_Wintab::loadWintab(HWND hwnd)
+{
+ /* Load Wintab library if available. */
+
+ auto handle = unique_hmodule(::LoadLibrary("Wintab32.dll"), &::FreeLibrary);
+ if (!handle) {
+ return nullptr;
+ }
+
+ /* Get Wintab functions. */
+
+ auto info = (GHOST_WIN32_WTInfo)::GetProcAddress(handle.get(), "WTInfoA");
+ if (!info) {
+ return nullptr;
+ }
+
+ auto open = (GHOST_WIN32_WTOpen)::GetProcAddress(handle.get(), "WTOpenA");
+ if (!open) {
+ return nullptr;
+ }
+
+ auto get = (GHOST_WIN32_WTGet)::GetProcAddress(handle.get(), "WTGetA");
+ if (!get) {
+ return nullptr;
+ }
+
+ auto set = (GHOST_WIN32_WTSet)::GetProcAddress(handle.get(), "WTSetA");
+ if (!set) {
+ return nullptr;
+ }
+
+ auto close = (GHOST_WIN32_WTClose)::GetProcAddress(handle.get(), "WTClose");
+ if (!close) {
+ return nullptr;
+ }
+
+ auto packetsGet = (GHOST_WIN32_WTPacketsGet)::GetProcAddress(handle.get(), "WTPacketsGet");
+ if (!packetsGet) {
+ return nullptr;
+ }
+
+ auto queueSizeGet = (GHOST_WIN32_WTQueueSizeGet)::GetProcAddress(handle.get(), "WTQueueSizeGet");
+ if (!queueSizeGet) {
+ return nullptr;
+ }
+
+ auto queueSizeSet = (GHOST_WIN32_WTQueueSizeSet)::GetProcAddress(handle.get(), "WTQueueSizeSet");
+ if (!queueSizeSet) {
+ return nullptr;
+ }
+
+ auto enable = (GHOST_WIN32_WTEnable)::GetProcAddress(handle.get(), "WTEnable");
+ if (!enable) {
+ return nullptr;
+ }
+
+ auto overlap = (GHOST_WIN32_WTOverlap)::GetProcAddress(handle.get(), "WTOverlap");
+ if (!overlap) {
+ return nullptr;
+ }
+
+ /* Build Wintab context. */
+
+ LOGCONTEXT lc = {0};
+ if (!info(WTI_DEFSYSCTX, 0, &lc)) {
+ return nullptr;
+ }
+
+ Coord tablet, system;
+ extractCoordinates(lc, tablet, system);
+ modifyContext(lc);
+
+ /* The Wintab spec says we must open the context disabled if we are using cursor masks. */
+ auto hctx = unique_hctx(open(hwnd, &lc, FALSE), close);
+ if (!hctx) {
+ return nullptr;
+ }
+
+ /* Wintab provides no way to determine the maximum queue size aside from checking if attempts
+ * to change the queue size are successful. */
+ const int maxQueue = 500;
+ int queueSize = queueSizeGet(hctx.get());
+
+ while (queueSize < maxQueue) {
+ int testSize = min(queueSize + 16, maxQueue);
+ if (queueSizeSet(hctx.get(), testSize)) {
+ queueSize = testSize;
+ }
+ else {
+ /* From Windows Wintab Documentation for WTQueueSizeSet:
+ * "If the return value is zero, the context has no queue because the function deletes the
+ * original queue before attempting to create a new one. The application must continue
+ * calling the function with a smaller queue size until the function returns a non - zero
+ * value."
+ *
+ * In our case we start with a known valid queue size and in the event of failure roll
+ * back to the last valid queue size. The Wintab spec dates back to 16 bit Windows, thus
+ * assumes memory recently deallocated may not be available, which is no longer a practical
+ * concern. */
+ if (!queueSizeSet(hctx.get(), queueSize)) {
+ /* If a previously valid queue size is no longer valid, there is likely something wrong in
+ * the Wintab implementation and we should not use it. */
+ return nullptr;
+ }
+ break;
+ }
+ }
+
+ return new GHOST_Wintab(hwnd,
+ std::move(handle),
+ info,
+ get,
+ set,
+ packetsGet,
+ enable,
+ overlap,
+ std::move(hctx),
+ tablet,
+ system,
+ queueSize);
+}
+
+void GHOST_Wintab::modifyContext(LOGCONTEXT &lc)
+{
+ lc.lcPktData = PACKETDATA;
+ lc.lcPktMode = PACKETMODE;
+ lc.lcMoveMask = PACKETDATA;
+ lc.lcOptions |= CXO_CSRMESSAGES | CXO_MESSAGES;
+
+ /* Tablet scaling is handled manually because some drivers don't handle HIDPI or multi-display
+ * correctly; reset tablet scale factors to unscaled tablet coodinates. */
+ lc.lcOutOrgX = lc.lcInOrgX;
+ lc.lcOutOrgY = lc.lcInOrgY;
+ lc.lcOutExtX = lc.lcInExtX;
+ lc.lcOutExtY = lc.lcInExtY;
+}
+
+void GHOST_Wintab::extractCoordinates(LOGCONTEXT &lc, Coord &tablet, Coord &system)
+{
+ tablet.x.org = lc.lcInOrgX;
+ tablet.x.ext = lc.lcInExtX;
+ tablet.y.org = lc.lcInOrgY;
+ tablet.y.ext = lc.lcInExtY;
+
+ system.x.org = lc.lcSysOrgX;
+ system.x.ext = lc.lcSysExtX;
+ system.y.org = lc.lcSysOrgY;
+ /* Wintab maps y origin to the tablet's bottom; invert y to match Windows y origin mapping to the
+ * screen top. */
+ system.y.ext = -lc.lcSysExtY;
+}
+
+GHOST_Wintab::GHOST_Wintab(HWND hwnd,
+ unique_hmodule handle,
+ GHOST_WIN32_WTInfo info,
+ GHOST_WIN32_WTGet get,
+ GHOST_WIN32_WTSet set,
+ GHOST_WIN32_WTPacketsGet packetsGet,
+ GHOST_WIN32_WTEnable enable,
+ GHOST_WIN32_WTOverlap overlap,
+ unique_hctx hctx,
+ Coord tablet,
+ Coord system,
+ int queueSize)
+ : m_handle{std::move(handle)},
+ m_fpInfo{info},
+ m_fpGet{get},
+ m_fpSet{set},
+ m_fpPacketsGet{packetsGet},
+ m_fpEnable{enable},
+ m_fpOverlap{overlap},
+ m_context{std::move(hctx)},
+ m_tabletCoord{tablet},
+ m_systemCoord{system},
+ m_pkts{queueSize}
+{
+ m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices);
+ updateCursorInfo();
+}
+
+void GHOST_Wintab::enable()
+{
+ m_fpEnable(m_context.get(), true);
+ m_enabled = true;
+}
+
+void GHOST_Wintab::disable()
+{
+ if (m_focused) {
+ loseFocus();
+ }
+ m_fpEnable(m_context.get(), false);
+ m_enabled = false;
+}
+
+void GHOST_Wintab::gainFocus()
+{
+ m_fpOverlap(m_context.get(), true);
+ m_focused = true;
+}
+
+void GHOST_Wintab::loseFocus()
+{
+ if (m_lastTabletData.Active != GHOST_kTabletModeNone) {
+ leaveRange();
+ }
+
+ /* Mouse mode of tablet or display layout may change when Wintab or Window is inactive. Don't
+ * trust for mouse movement until re-verified. */
+ m_coordTrusted = false;
+
+ m_fpOverlap(m_context.get(), false);
+ m_focused = false;
+}
+
+void GHOST_Wintab::leaveRange()
+{
+ /* Button state can't be tracked while out of range, reset it. */
+ m_buttons = 0;
+ /* Set to none to indicate tablet is inactive. */
+ m_lastTabletData = GHOST_TABLET_DATA_NONE;
+ /* Clear the packet queue. */
+ m_fpPacketsGet(m_context.get(), m_pkts.size(), m_pkts.data());
+}
+
+void GHOST_Wintab::remapCoordinates()
+{
+ LOGCONTEXT lc = {0};
+
+ if (m_fpInfo(WTI_DEFSYSCTX, 0, &lc)) {
+ extractCoordinates(lc, m_tabletCoord, m_systemCoord);
+ modifyContext(lc);
+
+ m_fpSet(m_context.get(), &lc);
+ }
+}
+
+void GHOST_Wintab::updateCursorInfo()
+{
+ AXIS Pressure, Orientation[3];
+
+ BOOL pressureSupport = m_fpInfo(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
+ m_maxPressure = pressureSupport ? Pressure.axMax : 0;
+
+ BOOL tiltSupport = m_fpInfo(WTI_DEVICES, DVC_ORIENTATION, &Orientation);
+ /* Check if tablet supports azimuth [0] and altitude [1], encoded in axResolution. */
+ if (tiltSupport && Orientation[0].axResolution && Orientation[1].axResolution) {
+ m_maxAzimuth = Orientation[0].axMax;
+ m_maxAltitude = Orientation[1].axMax;
+ }
+ else {
+ m_maxAzimuth = m_maxAltitude = 0;
+ }
+}
+
+void GHOST_Wintab::processInfoChange(LPARAM lParam)
+{
+ /* Update number of connected Wintab digitizers. */
+ if (LOWORD(lParam) == WTI_INTERFACE && HIWORD(lParam) == IFC_NDEVICES) {
+ m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices);
+ }
+}
+
+bool GHOST_Wintab::devicesPresent()
+{
+ return m_numDevices > 0;
+}
+
+GHOST_TabletData GHOST_Wintab::getLastTabletData()
+{
+ return m_lastTabletData;
+}
+
+void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
+{
+ const int numPackets = m_fpPacketsGet(m_context.get(), m_pkts.size(), m_pkts.data());
+ outWintabInfo.resize(numPackets);
+ size_t outExtent = 0;
+
+ for (int i = 0; i < numPackets; i++) {
+ PACKET pkt = m_pkts[i];
+ GHOST_WintabInfoWin32 &out = outWintabInfo[i + outExtent];
+
+ out.tabletData = GHOST_TABLET_DATA_NONE;
+ /* % 3 for multiple devices ("DualTrack"). */
+ switch (pkt.pkCursor % 3) {
+ case 0:
+ /* Puck - processed as mouse. */
+ out.tabletData.Active = GHOST_kTabletModeNone;
+ break;
+ case 1:
+ out.tabletData.Active = GHOST_kTabletModeStylus;
+ break;
+ case 2:
+ out.tabletData.Active = GHOST_kTabletModeEraser;
+ break;
+ }
+
+ out.x = pkt.pkX;
+ out.y = pkt.pkY;
+
+ if (m_maxPressure > 0) {
+ out.tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_maxPressure;
+ }
+
+ if ((m_maxAzimuth > 0) && (m_maxAltitude > 0)) {
+ ORIENTATION ort = pkt.pkOrientation;
+ float vecLen;
+ float altRad, azmRad; /* In radians. */
+
+ /*
+ * From the wintab spec:
+ * orAzimuth: Specifies the clockwise rotation of the cursor about the z axis through a
+ * full circular range.
+ * orAltitude: Specifies the angle with the x-y plane through a signed, semicircular range.
+ * Positive values specify an angle upward toward the positive z axis; negative values
+ * specify an angle downward toward the negative z axis.
+ *
+ * wintab.h defines orAltitude as a UINT but documents orAltitude as positive for upward
+ * angles and negative for downward angles. WACOM uses negative altitude values to show that
+ * the pen is inverted; therefore we cast orAltitude as an (int) and then use the absolute
+ * value.
+ */
+
+ /* Convert raw fixed point data to radians. */
+ altRad = (float)((fabs((float)ort.orAltitude) / (float)m_maxAltitude) * M_PI / 2.0);
+ azmRad = (float)(((float)ort.orAzimuth / (float)m_maxAzimuth) * M_PI * 2.0);
+
+ /* Find length of the stylus' projected vector on the XY plane. */
+ vecLen = cos(altRad);
+
+ /* From there calculate X and Y components based on azimuth. */
+ out.tabletData.Xtilt = sin(azmRad) * vecLen;
+ out.tabletData.Ytilt = (float)(sin(M_PI / 2.0 - azmRad) * vecLen);
+ }
+
+ out.time = pkt.pkTime;
+
+ /* Some Wintab libraries don't handle relative button input, so we track button presses
+ * manually. */
+ out.button = GHOST_kButtonMaskNone;
+ out.type = GHOST_kEventCursorMove;
+
+ DWORD buttonsChanged = m_buttons ^ pkt.pkButtons;
+ WORD buttonIndex = 0;
+ GHOST_WintabInfoWin32 buttonRef = out;
+ int buttons = 0;
+
+ while (buttonsChanged) {
+ if (buttonsChanged & 1) {
+ /* Find the index for the changed button from the button map. */
+ GHOST_TButtonMask button = mapWintabToGhostButton(pkt.pkCursor, buttonIndex);
+
+ if (button != GHOST_kButtonMaskNone) {
+ /* Extend output if multiple buttons are pressed. We don't extend input until we confirm
+ * a Wintab buttons maps to a system button. */
+ if (buttons > 0) {
+ outWintabInfo.resize(outWintabInfo.size() + 1);
+ outExtent++;
+ GHOST_WintabInfoWin32 &out = outWintabInfo[i + outExtent];
+ out = buttonRef;
+ }
+ buttons++;
+
+ out.button = button;
+ if (buttonsChanged & pkt.pkButtons) {
+ out.type = GHOST_kEventButtonDown;
+ }
+ else {
+ out.type = GHOST_kEventButtonUp;
+ }
+ }
+
+ m_buttons ^= 1 << buttonIndex;
+ }
+
+ buttonsChanged >>= 1;
+ buttonIndex++;
+ }
+ }
+
+ if (!outWintabInfo.empty()) {
+ m_lastTabletData = outWintabInfo.back().tabletData;
+ }
+}
+
+GHOST_TButtonMask GHOST_Wintab::mapWintabToGhostButton(UINT cursor, WORD physicalButton)
+{
+ const WORD numButtons = 32;
+ BYTE logicalButtons[numButtons] = {0};
+ BYTE systemButtons[numButtons] = {0};
+
+ if (!m_fpInfo(WTI_CURSORS + cursor, CSR_BUTTONMAP, &logicalButtons) ||
+ !m_fpInfo(WTI_CURSORS + cursor, CSR_SYSBTNMAP, &systemButtons)) {
+ return GHOST_kButtonMaskNone;
+ }
+
+ if (physicalButton >= numButtons) {
+ return GHOST_kButtonMaskNone;
+ }
+
+ BYTE lb = logicalButtons[physicalButton];
+
+ if (lb >= numButtons) {
+ return GHOST_kButtonMaskNone;
+ }
+
+ switch (systemButtons[lb]) {
+ case SBN_LCLICK:
+ return GHOST_kButtonMaskLeft;
+ case SBN_RCLICK:
+ return GHOST_kButtonMaskRight;
+ case SBN_MCLICK:
+ return GHOST_kButtonMaskMiddle;
+ default:
+ return GHOST_kButtonMaskNone;
+ }
+}
+
+void GHOST_Wintab::mapWintabToSysCoordinates(int x_in, int y_in, int &x_out, int &y_out)
+{
+ /* Maps from range [in.org, in.org + abs(in.ext)] to [out.org, out.org + abs(out.ext)], in
+ * reverse if in.ext and out.ext have differing sign. */
+ auto remap = [](int inPoint, Range in, Range out) -> int {
+ int absInExt = abs(in.ext);
+ int absOutExt = abs(out.ext);
+
+ /* Translate input from range [in.org, in.org + absInExt] to [0, absInExt] */
+ int inMagnitude = inPoint - in.org;
+
+ /* If signs of extents differ, reverse input over range. */
+ if ((in.ext < 0) != (out.ext < 0)) {
+ inMagnitude = absInExt - inMagnitude;
+ }
+
+ /* Scale from [0, absInExt] to [0, absOutExt]. */
+ int outMagnitude = inMagnitude * absOutExt / absInExt;
+
+ /* Translate from range [0, absOutExt] to [out.org, out.org + absOutExt]. */
+ int outPoint = outMagnitude + out.org;
+
+ return outPoint;
+ };
+
+ x_out = remap(x_in, m_tabletCoord.x, m_systemCoord.x);
+ y_out = remap(y_in, m_tabletCoord.y, m_systemCoord.y);
+}
+
+bool GHOST_Wintab::trustCoordinates()
+{
+ return m_coordTrusted;
+}
+
+bool GHOST_Wintab::testCoordinates(int sysX, int sysY, int wtX, int wtY)
+{
+ mapWintabToSysCoordinates(wtX, wtY, wtX, wtY);
+
+ /* Allow off by one pixel tolerance in case of rounding error. */
+ if (abs(sysX - wtX) <= 1 && abs(sysY - wtY) <= 1) {
+ m_coordTrusted = true;
+ return true;
+ }
+ else {
+ m_coordTrusted = false;
+ return false;
+ }
+}
diff --git a/intern/ghost/intern/GHOST_Wintab.h b/intern/ghost/intern/GHOST_Wintab.h
new file mode 100644
index 00000000000..7193049362d
--- /dev/null
+++ b/intern/ghost/intern/GHOST_Wintab.h
@@ -0,0 +1,250 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup GHOST
+ * Declaration of GHOST_WintabWin32 class.
+ */
+
+/* Wacom's Wintab documentation is periodically offline, moved, and increasingly hidden away. You
+ * can find a (painstakingly) archived copy of the documentation at
+ * https://web.archive.org/web/20201122230125/https://developer-docs-legacy.wacom.com/display/DevDocs/Windows+Wintab+Documentation
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <wtypes.h>
+
+#include "GHOST_Types.h"
+
+#include <wintab.h>
+/* PACKETDATA and PACKETMODE modify structs in pktdef.h, so make sure they come first. */
+#define PACKETDATA \
+ (PK_BUTTONS | PK_NORMAL_PRESSURE | PK_ORIENTATION | PK_CURSOR | PK_X | PK_Y | PK_TIME)
+#define PACKETMODE 0
+#include <pktdef.h>
+
+/* Typedefs for Wintab functions to allow dynamic loading. */
+typedef UINT(API *GHOST_WIN32_WTInfo)(UINT, UINT, LPVOID);
+typedef BOOL(API *GHOST_WIN32_WTGet)(HCTX, LPLOGCONTEXTA);
+typedef BOOL(API *GHOST_WIN32_WTSet)(HCTX, LPLOGCONTEXTA);
+typedef HCTX(API *GHOST_WIN32_WTOpen)(HWND, LPLOGCONTEXTA, BOOL);
+typedef BOOL(API *GHOST_WIN32_WTClose)(HCTX);
+typedef int(API *GHOST_WIN32_WTPacketsGet)(HCTX, int, LPVOID);
+typedef int(API *GHOST_WIN32_WTQueueSizeGet)(HCTX);
+typedef BOOL(API *GHOST_WIN32_WTQueueSizeSet)(HCTX, int);
+typedef BOOL(API *GHOST_WIN32_WTEnable)(HCTX, BOOL);
+typedef BOOL(API *GHOST_WIN32_WTOverlap)(HCTX, BOOL);
+
+/* Typedefs for Wintab and Windows resource management. */
+typedef std::unique_ptr<std::remove_pointer_t<HMODULE>, decltype(&::FreeLibrary)> unique_hmodule;
+typedef std::unique_ptr<std::remove_pointer_t<HCTX>, GHOST_WIN32_WTClose> unique_hctx;
+
+struct GHOST_WintabInfoWin32 {
+ GHOST_TInt32 x, y;
+ GHOST_TEventType type;
+ GHOST_TButtonMask button;
+ GHOST_TUns64 time;
+ GHOST_TabletData tabletData;
+};
+
+class GHOST_Wintab {
+ public:
+ /**
+ * Loads Wintab if available.
+ * \param hwnd: Window to attach Wintab context to.
+ */
+ static GHOST_Wintab *loadWintab(HWND hwnd);
+
+ /**
+ * Enables Wintab context.
+ */
+ void enable();
+
+ /**
+ * Disables the Wintab context and unwinds Wintab state.
+ */
+ void disable();
+
+ /**
+ * Brings Wintab context to the top of the overlap order.
+ */
+ void gainFocus();
+
+ /**
+ * Puts Wintab context at bottom of overlap order and unwinds Wintab state.
+ */
+ void loseFocus();
+
+ /**
+ * Clean up when Wintab leaves tracking range.
+ */
+ void leaveRange();
+
+ /**
+ * Handle Wintab coordinate changes when DisplayChange events occur.
+ */
+ void remapCoordinates();
+
+ /**
+ * Maps Wintab to Win32 display coordinates.
+ * \param x_in: The tablet x coordinate.
+ * \param y_in: The tablet y coordinate.
+ * \param x_out: Output for the Win32 mapped x coordinate.
+ * \param y_out: Output for the Win32 mapped y coordiante.
+ */
+ void mapWintabToSysCoordinates(int x_in, int y_in, int &x_out, int &y_out);
+
+ /**
+ * Updates cached Wintab properties for current cursor.
+ */
+ void updateCursorInfo();
+
+ /**
+ * Handle Wintab info changes such as change in number of connected tablets.
+ * \param lParam: LPARAM of the event.
+ */
+ void processInfoChange(LPARAM lParam);
+
+ /**
+ * Whether Wintab devices are present.
+ * \return True if Wintab devices are present.
+ */
+ bool devicesPresent();
+
+ /**
+ * Translate Wintab packets into GHOST_WintabInfoWin32 structs.
+ * \param outWintabInfo: Storage to return resulting GHOST_WintabInfoWin32 data.
+ */
+ void getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo);
+
+ /**
+ * Whether Wintab coordinates should be trusted.
+ * \return True if Wintab coordinates should be trusted.
+ */
+ bool trustCoordinates();
+
+ /**
+ * Tests whether Wintab coordinates can be trusted by comparing Win32 and Wintab reported curser
+ * position.
+ * \param sysX: System cursor x position.
+ * \param sysY: System cursor y position.
+ * \param wtX: Wintab cursor x position.
+ * \param wtY: Wintab cursor y position.
+ * \return True if Win32 and Wintab cursor positions match within tolerance.
+ *
+ * Note: Only test coordiantes on button press, not release. This prevents issues when async
+ * mismatch causes mouse movement to replay and snap back, which is only an issue while drawing.
+ */
+ bool testCoordinates(int sysX, int sysY, int wtX, int wtY);
+
+ /**
+ * Retrieve the most recent tablet data, or none if pen is not in range.
+ * \return Most recent tablet data, or none if pen is not in range.
+ */
+ GHOST_TabletData getLastTabletData();
+
+ private:
+ /** Wintab dll handle. */
+ unique_hmodule m_handle;
+ /** Wintab API functions. */
+ GHOST_WIN32_WTInfo m_fpInfo = nullptr;
+ GHOST_WIN32_WTGet m_fpGet = nullptr;
+ GHOST_WIN32_WTSet m_fpSet = nullptr;
+ GHOST_WIN32_WTPacketsGet m_fpPacketsGet = nullptr;
+ GHOST_WIN32_WTEnable m_fpEnable = nullptr;
+ GHOST_WIN32_WTOverlap m_fpOverlap = nullptr;
+
+ /** Stores the Wintab tablet context. */
+ unique_hctx m_context;
+ /** Whether the context is enabled. */
+ bool m_enabled = false;
+ /** Whether the context has focus and is at the top of overlap order. */
+ bool m_focused = false;
+
+ /** Pressed button map. */
+ GHOST_TUns8 m_buttons = 0;
+
+ /** Range of a coodinate space. */
+ struct Range {
+ /** Origin of range. */
+ int org = 0;
+ /** Extent of range. */
+ int ext = 1;
+ };
+
+ /** 2D Coordinate space. */
+ struct Coord {
+ /** Range of x. */
+ Range x = {};
+ /** Range of y. */
+ Range y = {};
+ };
+ /** Whether Wintab coordinates are trusted. */
+ bool m_coordTrusted = false;
+ /** Tablet input range. */
+ Coord m_tabletCoord = {};
+ /** System output range. */
+ Coord m_systemCoord = {};
+
+ int m_maxPressure = 0;
+ int m_maxAzimuth = 0;
+ int m_maxAltitude = 0;
+
+ /** Number of connected Wintab devices. */
+ UINT m_numDevices = 0;
+ /** Reusable buffer to read in Wintab packets. */
+ std::vector<PACKET> m_pkts;
+ /** Most recently received tablet data, or none if pen is not in range. */
+ GHOST_TabletData m_lastTabletData = GHOST_TABLET_DATA_NONE;
+
+ GHOST_Wintab(HWND hwnd,
+ unique_hmodule handle,
+ GHOST_WIN32_WTInfo info,
+ GHOST_WIN32_WTGet get,
+ GHOST_WIN32_WTSet set,
+ GHOST_WIN32_WTPacketsGet packetsGet,
+ GHOST_WIN32_WTEnable enable,
+ GHOST_WIN32_WTOverlap overlap,
+ unique_hctx hctx,
+ Coord tablet,
+ Coord system,
+ int queueSize);
+
+ /**
+ * Convert Wintab system mapped (mouse) buttons into Ghost button mask.
+ * \param cursor: The Wintab cursor associated to the button.
+ * \param physicalButton: The physical button ID to inspect.
+ * \return The system mapped button.
+ */
+ GHOST_TButtonMask mapWintabToGhostButton(UINT cursor, WORD physicalButton);
+
+ /**
+ * Applies common modifications to Wintab context.
+ * \param lc: Wintab context to modify.
+ */
+ static void modifyContext(LOGCONTEXT &lc);
+
+ /**
+ * Extracts tablet and system coordinates from Wintab context.
+ * \param lc: Wintab context to extract coordinates from.
+ * \param tablet: Tablet coordinates.
+ * \param system: System coordinates.
+ */
+ static void extractCoordinates(LOGCONTEXT &lc, Coord &tablet, Coord &system);
+};