Windows: support high resolution tablet pen events for Windows Ink

Rather than using the last state of the tablet, we now query the history of
pointer events so strokes can follow the pen even if Blender does not handle
events at the same rate.

Differential Revision: https://developer.blender.org/D6675
This commit is contained in:
Nicholas Rishel 2020-03-27 18:31:09 +01:00 committed by Brecht Van Lommel
parent 3d8c57f4da
commit d571d615a5
Notes: blender-bot 2023-02-14 01:07:44 +01:00
Referenced by issue #70765, Input Mouse or Tablet events are too sparse when input device moves very fast in Windows
4 changed files with 197 additions and 110 deletions

View File

@ -118,7 +118,7 @@ typedef struct GHOST_TabletData {
} GHOST_TabletData;
static const GHOST_TabletData GHOST_TABLET_DATA_NONE = {
GHOST_kTabletModeNone, /* No tablet connected. */
GHOST_kTabletModeNone, /* No cursor in range */
1.0f, /* Pressure */
0.0f, /* Xtilt */
0.0f}; /* Ytilt */

View File

@ -115,12 +115,22 @@
# define WM_DPICHANGED 0x02E0
#endif // WM_DPICHANGED
// WM_POINTER API messages minimum Windows 7
#ifndef WM_POINTERENTER
# define WM_POINTERENTER 0x0249
#endif // WM_POINTERENTER
#ifndef WM_POINTERDOWN
# define WM_POINTERDOWN 0x0246
#endif // WM_POINTERDOWN
#ifndef WM_POINTERUPDATE
# define WM_POINTERUPDATE 0x0245
#endif // WM_POINTERUPDATE
#define WM_POINTERDOWN 0x0246
#define WM_POINTERUP 0x0247
#ifndef WM_POINTERUP
# define WM_POINTERUP 0x0247
#endif // WM_POINTERUP
#ifndef WM_POINTERLEAVE
# define WM_POINTERLEAVE 0x024A
#endif // WM_POINTERLEAVE
/* Workaround for some laptop touchpads, some of which seems to
* have driver issues which makes it so window function receives
@ -928,9 +938,13 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type,
window->updateMouseCapture(MouseReleased);
}
if (window->useTabletAPI(GHOST_kTabletNative)) {
window->setTabletData(NULL);
if (window->m_tabletInRange) {
if (window->useTabletAPI(GHOST_kTabletNative)) {
// Win32 Pointer processing handles input while in-range and in-contact events.
return NULL;
}
}
return new GHOST_EventButton(
system->getMilliSeconds(), type, window, mask, window->getTabletData());
}
@ -938,57 +952,81 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type,
void GHOST_SystemWin32::processPointerEvents(
UINT type, GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam, bool &eventHandled)
{
GHOST_PointerInfoWin32 pointerInfo;
std::vector<GHOST_PointerInfoWin32> pointerInfo;
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
if (!window->useTabletAPI(GHOST_kTabletNative)) {
return;
}
if (window->getPointerInfo(&pointerInfo, wParam, lParam) != GHOST_kSuccess) {
if (window->getPointerInfo(pointerInfo, wParam, lParam) != GHOST_kSuccess) {
return;
}
if (!pointerInfo.isPrimary) {
if (!pointerInfo[0].isPrimary) {
eventHandled = true;
return; // For multi-touch displays we ignore these events
}
system->setCursorPosition(pointerInfo.pixelLocation.x, pointerInfo.pixelLocation.y);
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));
break;
case WM_POINTERDOWN:
/* Update window tablet data to be included in event. */
window->setTabletData(&pointerInfo.tabletData);
system->pushEvent(new GHOST_EventButton(system->getMilliSeconds(),
// Move cursor to point of contact because GHOST_EventButton does not include position.
system->pushEvent(new GHOST_EventCursor(pointerInfo[0].time,
GHOST_kEventCursorMove,
window,
pointerInfo[0].pixelLocation.x,
pointerInfo[0].pixelLocation.y,
pointerInfo[0].tabletData));
system->pushEvent(new GHOST_EventButton(pointerInfo[0].time,
GHOST_kEventButtonDown,
window,
pointerInfo.buttonMask,
pointerInfo.tabletData));
pointerInfo[0].buttonMask,
pointerInfo[0].tabletData));
window->updateMouseCapture(MousePressed);
break;
case WM_POINTERUPDATE:
/* Update window tablet data to be included in event. */
system->pushEvent(GHOST_EventCursor(system->getMilliSeconds(),
GHOST_kEventCursorMove,
window,
pointerInfo.pixelLocation.x,
pointerInfo.pixelLocation.y,
pointerInfo.tabletData));
// 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));
}
break;
case WM_POINTERUP:
system->pushEvent(new GHOST_EventButton(system->getMilliSeconds(),
system->pushEvent(new GHOST_EventButton(pointerInfo[0].time,
GHOST_kEventButtonUp,
window,
pointerInfo.buttonMask,
window->getTabletData()));
pointerInfo[0].buttonMask,
pointerInfo[0].tabletData));
window->updateMouseCapture(MouseReleased);
break;
case WM_POINTERLEAVE:
window->m_tabletInRange = false;
system->pushEvent(new GHOST_EventButton(pointerInfo[0].time,
GHOST_kEventCursorMove,
window,
pointerInfo[0].buttonMask,
pointerInfo[0].tabletData));
break;
default:
break;
}
eventHandled = true;
system->setCursorPosition(pointerInfo[0].pixelLocation.x, pointerInfo[0].pixelLocation.y);
}
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
@ -996,12 +1034,18 @@ 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;
}
}
system->getCursorPosition(x_screen, y_screen);
/* TODO: CHECK IF THIS IS A TABLET EVENT */
bool is_tablet = false;
if (is_tablet == false && window->getCursorGrabModeIsWarp()) {
if (window->getCursorGrabModeIsWarp() && !window->m_tabletInRange) {
GHOST_TInt32 x_new = x_screen;
GHOST_TInt32 y_new = y_screen;
GHOST_TInt32 x_accum, y_accum;
@ -1424,9 +1468,11 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
////////////////////////////////////////////////////////////////////////
// Pointer events, processed
////////////////////////////////////////////////////////////////////////
case WM_POINTERENTER:
case WM_POINTERDOWN:
case WM_POINTERUPDATE:
case WM_POINTERUP:
case WM_POINTERLEAVE:
processPointerEvents(msg, window, wParam, lParam, eventHandled);
break;
////////////////////////////////////////////////////////////////////////

View File

@ -82,11 +82,12 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_wantAlphaBackground(alphaBackground),
m_normal_state(GHOST_kWindowStateNormal),
m_user32(NULL),
m_fpGetPointerInfo(NULL),
m_fpGetPointerPenInfo(NULL),
m_fpGetPointerTouchInfo(NULL),
m_fpGetPointerInfoHistory(NULL),
m_fpGetPointerPenInfoHistory(NULL),
m_fpGetPointerTouchInfoHistory(NULL),
m_parentWindowHwnd(parentwindow ? parentwindow->m_hWnd : NULL),
m_debug_context(is_debug)
m_debug_context(is_debug),
m_tabletInRange(false)
{
// Initialize tablet variables
memset(&m_wintab, 0, sizeof(m_wintab));
@ -288,11 +289,12 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
// Initialize Windows Ink
if (m_user32) {
m_fpGetPointerInfo = (GHOST_WIN32_GetPointerInfo)::GetProcAddress(m_user32, "GetPointerInfo");
m_fpGetPointerPenInfo = (GHOST_WIN32_GetPointerPenInfo)::GetProcAddress(m_user32,
"GetPointerPenInfo");
m_fpGetPointerTouchInfo = (GHOST_WIN32_GetPointerTouchInfo)::GetProcAddress(
m_user32, "GetPointerTouchInfo");
m_fpGetPointerInfoHistory = (GHOST_WIN32_GetPointerInfoHistory)::GetProcAddress(
m_user32, "GetPointerInfoHistory");
m_fpGetPointerPenInfoHistory = (GHOST_WIN32_GetPointerPenInfoHistory)::GetProcAddress(
m_user32, "GetPointerPenInfoHistory");
m_fpGetPointerTouchInfoHistory = (GHOST_WIN32_GetPointerTouchInfoHistory)::GetProcAddress(
m_user32, "GetPointerTouchInfoHistory");
}
// Initialize Wintab
@ -379,9 +381,9 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
if (m_user32) {
FreeLibrary(m_user32);
m_user32 = NULL;
m_fpGetPointerInfo = NULL;
m_fpGetPointerPenInfo = NULL;
m_fpGetPointerTouchInfo = NULL;
m_fpGetPointerInfoHistory = NULL;
m_fpGetPointerPenInfoHistory = NULL;
m_fpGetPointerTouchInfoHistory = NULL;
}
if (m_customCursor) {
@ -1021,78 +1023,82 @@ GHOST_TSuccess GHOST_WindowWin32::hasCursorShape(GHOST_TStandardCursor cursorSha
return (getStandardCursor(cursorShape)) ? GHOST_kSuccess : GHOST_kFailure;
}
GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(GHOST_PointerInfoWin32 *pointerInfo,
WPARAM wParam,
LPARAM lParam)
GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(
std::vector<GHOST_PointerInfoWin32> &outPointerInfo, WPARAM wParam, LPARAM lParam)
{
ZeroMemory(pointerInfo, sizeof(GHOST_PointerInfoWin32));
// Obtain the basic information from the event
pointerInfo->pointerId = GET_POINTERID_WPARAM(wParam);
pointerInfo->isInContact = IS_POINTER_INCONTACT_WPARAM(wParam);
pointerInfo->isPrimary = IS_POINTER_PRIMARY_WPARAM(wParam);
// Obtain more accurate and predicted information from the Pointer API
POINTER_INFO pointerApiInfo;
if (!(m_fpGetPointerInfo && m_fpGetPointerInfo(pointerInfo->pointerId, &pointerApiInfo))) {
if (!useTabletAPI(GHOST_kTabletNative)) {
return GHOST_kFailure;
}
pointerInfo->hasButtonMask = GHOST_kSuccess;
switch (pointerApiInfo.ButtonChangeType) {
case POINTER_CHANGE_FIRSTBUTTON_DOWN:
case POINTER_CHANGE_FIRSTBUTTON_UP:
pointerInfo->buttonMask = GHOST_kButtonMaskLeft;
break;
case POINTER_CHANGE_SECONDBUTTON_DOWN:
case POINTER_CHANGE_SECONDBUTTON_UP:
pointerInfo->buttonMask = GHOST_kButtonMaskRight;
break;
case POINTER_CHANGE_THIRDBUTTON_DOWN:
case POINTER_CHANGE_THIRDBUTTON_UP:
pointerInfo->buttonMask = GHOST_kButtonMaskMiddle;
break;
case POINTER_CHANGE_FOURTHBUTTON_DOWN:
case POINTER_CHANGE_FOURTHBUTTON_UP:
pointerInfo->buttonMask = GHOST_kButtonMaskButton4;
break;
case POINTER_CHANGE_FIFTHBUTTON_DOWN:
case POINTER_CHANGE_FIFTHBUTTON_UP:
pointerInfo->buttonMask = GHOST_kButtonMaskButton5;
break;
default:
pointerInfo->hasButtonMask = GHOST_kFailure;
break;
}
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;
pointerInfo->pixelLocation = pointerApiInfo.ptPixelLocation;
pointerInfo->tabletData.Active = GHOST_kTabletModeNone;
pointerInfo->tabletData.Pressure = 1.0f;
pointerInfo->tabletData.Xtilt = 0.0f;
pointerInfo->tabletData.Ytilt = 0.0f;
if (pointerApiInfo.pointerType != PT_PEN) {
if (!(m_fpGetPointerInfoHistory && m_fpGetPointerInfoHistory(pointerId, &outCount, NULL))) {
return GHOST_kFailure;
}
POINTER_PEN_INFO pointerPenInfo;
if (m_fpGetPointerPenInfo && m_fpGetPointerPenInfo(pointerInfo->pointerId, &pointerPenInfo)) {
pointerInfo->tabletData.Active = GHOST_kTabletModeStylus;
auto pointerPenInfo = std::vector<POINTER_PEN_INFO>(outCount);
outPointerInfo.resize(outCount);
if (pointerPenInfo.penMask & PEN_MASK_PRESSURE) {
pointerInfo->tabletData.Pressure = pointerPenInfo.pressure / 1024.0f;
if (!(m_fpGetPointerPenInfoHistory &&
m_fpGetPointerPenInfoHistory(pointerId, &outCount, pointerPenInfo.data()))) {
return GHOST_kFailure;
}
for (GHOST_TUns32 i = 0; i < outCount; i++) {
POINTER_INFO pointerApiInfo = pointerPenInfo[i].pointerInfo;
// Obtain the basic information from the event
outPointerInfo[i].pointerId = pointerId;
outPointerInfo[i].isPrimary = isPrimary;
switch (pointerApiInfo.ButtonChangeType) {
case POINTER_CHANGE_FIRSTBUTTON_DOWN:
case POINTER_CHANGE_FIRSTBUTTON_UP:
outPointerInfo[i].buttonMask = GHOST_kButtonMaskLeft;
break;
case POINTER_CHANGE_SECONDBUTTON_DOWN:
case POINTER_CHANGE_SECONDBUTTON_UP:
outPointerInfo[i].buttonMask = GHOST_kButtonMaskRight;
break;
case POINTER_CHANGE_THIRDBUTTON_DOWN:
case POINTER_CHANGE_THIRDBUTTON_UP:
outPointerInfo[i].buttonMask = GHOST_kButtonMaskMiddle;
break;
case POINTER_CHANGE_FOURTHBUTTON_DOWN:
case POINTER_CHANGE_FOURTHBUTTON_UP:
outPointerInfo[i].buttonMask = GHOST_kButtonMaskButton4;
break;
case POINTER_CHANGE_FIFTHBUTTON_DOWN:
case POINTER_CHANGE_FIFTHBUTTON_UP:
outPointerInfo[i].buttonMask = GHOST_kButtonMaskButton5;
break;
default:
break;
}
if (pointerPenInfo.penFlags & PEN_FLAG_ERASER) {
pointerInfo->tabletData.Active = GHOST_kTabletModeEraser;
outPointerInfo[i].pixelLocation = pointerApiInfo.ptPixelLocation;
outPointerInfo[i].tabletData.Active = GHOST_kTabletModeStylus;
outPointerInfo[i].tabletData.Pressure = 1.0f;
outPointerInfo[i].tabletData.Xtilt = 0.0f;
outPointerInfo[i].tabletData.Ytilt = 0.0f;
outPointerInfo[i].time = system->performanceCounterToMillis(pointerApiInfo.PerformanceCount);
if (pointerPenInfo[i].penMask & PEN_MASK_PRESSURE) {
outPointerInfo[i].tabletData.Pressure = pointerPenInfo[i].pressure / 1024.0f;
}
if (pointerPenInfo.penFlags & PEN_MASK_TILT_X) {
pointerInfo->tabletData.Xtilt = fmin(fabs(pointerPenInfo.tiltX / 90), 1.0f);
if (pointerPenInfo[i].penFlags & PEN_FLAG_ERASER) {
outPointerInfo[i].tabletData.Active = GHOST_kTabletModeEraser;
}
if (pointerPenInfo.penFlags & PEN_MASK_TILT_Y) {
pointerInfo->tabletData.Ytilt = fmin(fabs(pointerPenInfo.tiltY / 90), 1.0f);
if (pointerPenInfo[i].penMask & PEN_MASK_TILT_X) {
outPointerInfo[i].tabletData.Xtilt = fmin(fabs(pointerPenInfo[i].tiltX / 90.0f), 1.0f);
}
if (pointerPenInfo[i].penMask & PEN_MASK_TILT_Y) {
outPointerInfo[i].tabletData.Ytilt = fmin(fabs(pointerPenInfo[i].tiltY / 90.0f), 1.0f);
}
}

View File

@ -88,6 +88,26 @@ typedef enum tagPOINTER_BUTTON_CHANGE_TYPE {
typedef DWORD POINTER_INPUT_TYPE;
typedef UINT32 POINTER_FLAGS;
#define POINTER_FLAG_NONE 0x00000000
#define POINTER_FLAG_NEW 0x00000001
#define POINTER_FLAG_INRANGE 0x00000002
#define POINTER_FLAG_INCONTACT 0x00000004
#define POINTER_FLAG_FIRSTBUTTON 0x00000010
#define POINTER_FLAG_SECONDBUTTON 0x00000020
#define POINTER_FLAG_THIRDBUTTON 0x00000040
#define POINTER_FLAG_FOURTHBUTTON 0x00000080
#define POINTER_FLAG_FIFTHBUTTON 0x00000100
#define POINTER_FLAG_PRIMARY 0x00002000
#define POINTER_FLAG_CONFIDENCE 0x000004000
#define POINTER_FLAG_CANCELED 0x000008000
#define POINTER_FLAG_DOWN 0x00010000
#define POINTER_FLAG_UPDATE 0x00020000
#define POINTER_FLAG_UP 0x00040000
#define POINTER_FLAG_WHEEL 0x00080000
#define POINTER_FLAG_HWHEEL 0x00100000
#define POINTER_FLAG_CAPTURECHANGED 0x00200000
#define POINTER_FLAG_HASTRANSFORM 0x00400000
typedef struct tagPOINTER_INFO {
POINTER_INPUT_TYPE pointerType;
UINT32 pointerId;
@ -192,17 +212,23 @@ typedef struct tagPOINTER_TOUCH_INFO {
#define IS_POINTER_CANCELED_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CANCELED)
typedef BOOL(API *GHOST_WIN32_GetPointerInfo)(UINT32 pointerId, POINTER_INFO *pointerInfo);
typedef BOOL(API *GHOST_WIN32_GetPointerPenInfo)(UINT32 pointerId, POINTER_PEN_INFO *penInfo);
typedef BOOL(API *GHOST_WIN32_GetPointerTouchInfo)(UINT32 pointerId, POINTER_TOUCH_INFO *penInfo);
typedef BOOL(WINAPI *GHOST_WIN32_GetPointerInfoHistory)(UINT32 pointerId,
UINT32 *entriesCount,
POINTER_INFO *pointerInfo);
typedef BOOL(WINAPI *GHOST_WIN32_GetPointerPenInfoHistory)(UINT32 pointerId,
UINT32 *entriesCount,
POINTER_PEN_INFO *penInfo);
typedef BOOL(WINAPI *GHOST_WIN32_GetPointerTouchInfoHistory)(UINT32 pointerId,
UINT32 *entriesCount,
POINTER_TOUCH_INFO *touchInfo);
struct GHOST_PointerInfoWin32 {
GHOST_TInt32 pointerId;
GHOST_TInt32 isInContact;
GHOST_TInt32 isPrimary;
GHOST_TSuccess hasButtonMask;
GHOST_TButtonMask buttonMask;
POINT pixelLocation;
GHOST_TUns64 time;
GHOST_TabletData tabletData;
};
@ -403,9 +429,17 @@ class GHOST_WindowWin32 : public GHOST_Window {
void setTabletData(GHOST_TabletData *tabletData);
bool useTabletAPI(GHOST_TTabletAPI api) const;
void getPointerInfo(WPARAM wParam);
void processWin32PointerEvent(WPARAM wParam);
/**
* Translate WM_POINTER events into GHOST_PointerInfoWin32 structs.
* \param outPointerInfo Storage to return resulting GHOST_PointerInfoWin32 structs
* \param wParam WPARAM of the event
* \param lParam LPARAM of the event
*/
GHOST_TSuccess getPointerInfo(std::vector<GHOST_PointerInfoWin32> &outPointerInfo,
WPARAM wParam,
LPARAM lParam);
void processWin32TabletActivateEvent(WORD state);
void processWin32TabletInitEvent();
void processWin32TabletEvent(WPARAM wParam, LPARAM lParam);
@ -429,7 +463,8 @@ class GHOST_WindowWin32 : public GHOST_Window {
*/
bool getMousePressed() const;
GHOST_TSuccess getPointerInfo(GHOST_PointerInfoWin32 *pointerInfo, WPARAM wParam, LPARAM lParam);
/** Whether a tablet stylus is being tracked */
bool m_tabletInRange;
/** if the window currently resizing */
bool m_inLiveResize;
@ -537,9 +572,9 @@ class GHOST_WindowWin32 : public GHOST_Window {
/** user32 dll handle*/
HMODULE m_user32;
GHOST_WIN32_GetPointerInfo m_fpGetPointerInfo;
GHOST_WIN32_GetPointerPenInfo m_fpGetPointerPenInfo;
GHOST_WIN32_GetPointerTouchInfo m_fpGetPointerTouchInfo;
GHOST_WIN32_GetPointerInfoHistory m_fpGetPointerInfoHistory;
GHOST_WIN32_GetPointerPenInfoHistory m_fpGetPointerPenInfoHistory;
GHOST_WIN32_GetPointerTouchInfoHistory m_fpGetPointerTouchInfoHistory;
HWND m_parentWindowHwnd;