Revert Wintab High Frequency Input.

This revert removes handling of cursor move and button press events
during Wintab's WT_PACKET event, instead storing pressure and tilt
information to be combined with Window's WM_MOUSEMOVE events.

This also reverts dynamic enabling and disabling of Wintab, dependent
on the chosen Tablet API. If the Tablet API is not explictly Windows
Ink during startup, Wintab is loaded and enabled.

Left in place is a fallback to Windows Ink when the Tablet API is set
to Automatic and no Wintab devices are present. This allows devices
with Wintab installed but not active to fallback to Windows Ink.

Using position provided by Wintab was found to have too many
regressions to include in Blender 2.93. The primary source of
regressions was tablets which mapped coordinates incorrectly on multi-
monitor and scaled displays. This resulted in an offset between what
the driver controlled Win32 cursor position and the Wintab reported
position. A special case of this included tablets set to mouse mode,
where Wintab reported absolute position while the system cursor moved
as a relative mouse with mouse acceleration.
This commit is contained in:
Nicholas Rishel 2021-02-15 22:47:52 -08:00
parent c63df3b33f
commit 2e81f2c01a
Notes: blender-bot 2023-02-14 02:27:56 +01:00
Referenced by commit ee4f306509, Fix T85844: high pressure at start of line.
Referenced by commit 37afeb7eaa, Fix T85844: high pressure at start of line.
Referenced by issue #84830, Double clicking on text edit controls doesn't select the full word
6 changed files with 242 additions and 585 deletions

View File

@ -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.
*/
virtual void setTabletAPI(GHOST_TTabletAPI api);
void setTabletAPI(GHOST_TTabletAPI api);
GHOST_TTabletAPI getTabletAPI(void);
#ifdef WITH_INPUT_NDOF

View File

@ -237,11 +237,6 @@ GHOST_SystemWin32::~GHOST_SystemWin32()
toggleConsole(1);
}
GHOST_TUns64 GHOST_SystemWin32::millisSinceStart(__int64 ms) const
{
return (GHOST_TUns64)(ms - m_start * 1000 / m_freq);
}
GHOST_TUns64 GHOST_SystemWin32::performanceCounterToMillis(__int64 perf_ticks) const
{
// Calculate the time passed since system initialization.
@ -955,99 +950,23 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type,
{
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
GHOST_TabletData td = window->m_tabletInRange ? window->getLastTabletData() :
GHOST_TABLET_DATA_NONE;
GHOST_TabletData td = GHOST_TABLET_DATA_NONE;
/* Move mouse to button event position. */
if (!window->m_tabletInRange) {
processCursorEvent(window);
}
else {
/* Tablet should be hadling inbetween mouse moves, only move to event position. */
if (window->m_tabletInRange) {
td = window->getTabletData();
/* Check if tablet cursor position is in sync with Win32 cursor position, if not then move
* cursor to position where button event occurred. */
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));
}
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_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
std::vector<GHOST_WintabInfoWin32> wintabInfo;
if (!window->getWintabInfo(wintabInfo)) {
return;
}
for (auto info : wintabInfo) {
switch (info.type) {
case GHOST_kEventCursorMove: {
system->pushEvent(new GHOST_EventCursor(
info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
break;
}
case GHOST_kEventButtonDown: {
system->pushEvent(new GHOST_EventCursor(
info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
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;
}
MSG msg;
if (PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD) &&
WM_QUIT != msg.message) {
window->updateMouseCapture(MousePressed);
system->pushEvent(
new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
}
break;
}
case GHOST_kEventButtonUp: {
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;
}
MSG msg;
if (PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD) &&
WM_QUIT != msg.message) {
window->updateMouseCapture(MouseReleased);
system->pushEvent(
new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
}
break;
}
default:
break;
if (msgPosX != system->m_mousePosX || msgPosY != system->m_mousePosY) {
system->pushEvent(new GHOST_EventCursor(
::GetMessageTime(), GHOST_kEventCursorMove, window, msgPosX, msgPosY, td));
}
}
return new GHOST_EventButton(system->getMilliSeconds(), type, window, mask, td);
}
void GHOST_SystemWin32::processPointerEvent(
@ -1129,12 +1048,15 @@ void GHOST_SystemWin32::processPointerEvent(
void GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
{
/* Cursor moves handled by tablets while active. */
if (window->m_tabletInRange) {
if (window->m_tabletInRange && 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;
}
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
GHOST_TabletData td = window->getTabletData();
DWORD msgPos = ::GetMessagePos();
LONG msgTime = ::GetMessageTime();
@ -1185,13 +1107,13 @@ void GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
window,
points[i].x + x_accum,
points[i].y + y_accum,
GHOST_TABLET_DATA_NONE));
td));
}
DWORD lastTimestamp = points[0].time;
/* Check if we need to wrap the cursor. */
if (window->getCursorGrabModeIsWarp()) {
if (window->getCursorGrabModeIsWarp() && !window->m_tabletInRange) {
/* Wrap based on current cursor position in case Win32 mouse move queue is out of order due to
* prior wrap. */
POINT point;
@ -1334,23 +1256,6 @@ 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)
{
@ -1358,6 +1263,7 @@ 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);
@ -1385,18 +1291,6 @@ 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);
GHOST_WindowManager *wm = getWindowManager();
for (GHOST_IWindow *win : wm->getWindows()) {
GHOST_WindowWin32 *windowWin32 = (GHOST_WindowWin32 *)win;
windowWin32->setWintabEnabled(windowWin32->useTabletAPI(GHOST_kTabletWintab));
}
}
void GHOST_SystemWin32::processMinMaxInfo(MINMAXINFO *minmax)
{
minmax->ptMinTrackSize.x = 320;
@ -1636,30 +1530,16 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
////////////////////////////////////////////////////////////////////////
// Wintab events, processed
////////////////////////////////////////////////////////////////////////
case WT_INFOCHANGE: {
case WT_INFOCHANGE:
window->processWintabInfoChangeEvent(lParam);
eventHandled = true;
break;
}
case WT_CSRCHANGE:
window->updateWintabCursorInfo();
eventHandled = true;
break;
case WT_PROXIMITY: {
if (window->useTabletAPI(GHOST_kTabletWintab)) {
if (LOWORD(lParam)) {
window->m_tabletInRange = true;
}
else {
window->processWintabLeave();
}
}
eventHandled = true;
break;
}
case WT_PACKET:
processWintabEvent(window);
eventHandled = true;
window->processWin32TabletEvent(wParam, lParam);
break;
case WT_CSRCHANGE:
case WT_PROXIMITY:
window->processWin32TabletInitEvent();
break;
////////////////////////////////////////////////////////////////////////
// Pointer events, processed
@ -1675,53 +1555,52 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
// Mouse events, processed
////////////////////////////////////////////////////////////////////////
case WM_LBUTTONDOWN:
window->updateMouseCapture(MousePressed);
event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskLeft);
break;
case WM_MBUTTONDOWN:
window->updateMouseCapture(MousePressed);
event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskMiddle);
break;
case WM_RBUTTONDOWN:
window->updateMouseCapture(MousePressed);
event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskRight);
break;
case WM_XBUTTONDOWN:
if ((short)HIWORD(wParam) == XBUTTON1) {
window->updateMouseCapture(MousePressed);
event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskButton4);
}
else if ((short)HIWORD(wParam) == XBUTTON2) {
window->updateMouseCapture(MousePressed);
event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskButton5);
}
break;
case WM_LBUTTONUP:
window->updateMouseCapture(MouseReleased);
event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskLeft);
break;
case WM_MBUTTONUP:
window->updateMouseCapture(MouseReleased);
event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskMiddle);
break;
case WM_RBUTTONUP:
window->updateMouseCapture(MouseReleased);
event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskRight);
break;
case WM_XBUTTONUP:
if ((short)HIWORD(wParam) == XBUTTON1) {
window->updateMouseCapture(MouseReleased);
event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskButton4);
}
else if ((short)HIWORD(wParam) == XBUTTON2) {
window->updateMouseCapture(MouseReleased);
event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskButton5);
}
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;
window->setWintabOverlap(true);
}
if (!window->m_tabletInRange) {
processCursorEvent(window);
eventHandled = true;
}
processCursorEvent(window);
eventHandled = true;
break;
case WM_MOUSEWHEEL: {
/* The WM_MOUSEWHEEL message is sent to the focus window
@ -1763,13 +1642,7 @@ 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;
window->setWintabOverlap(false);
if (!window->m_tabletInRange) {
processCursorEvent(window);
}
break;
////////////////////////////////////////////////////////////////////////
// Mouse events, ignored
////////////////////////////////////////////////////////////////////////
@ -1785,6 +1658,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
* is sent to the window that has captured the mouse.
*/
break;
////////////////////////////////////////////////////////////////////////
// Window events, processed
////////////////////////////////////////////////////////////////////////
@ -1816,7 +1690,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;
}
@ -1858,8 +1732,6 @@ 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
@ -1867,15 +1739,15 @@ 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.
*/
event = processWindowSizeEvent(window);
if (wParam == SIZE_MINIMIZED) {
window->setWintabEnabled(false);
/* 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 if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) {
window->setWintabEnabled(true);
else {
event = processWindowEvent(GHOST_kEventWindowSize, window);
}
break;
case WM_CAPTURECHANGED:
window->lostMouseCapture();
@ -1926,12 +1798,6 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
SWP_NOZORDER | SWP_NOACTIVATE);
}
break;
case WM_DISPLAYCHANGE:
for (GHOST_IWindow *iter_win : system->getWindowManager()->getWindows()) {
GHOST_WindowWin32 *iter_win32win = (GHOST_WindowWin32 *)iter_win;
iter_win32win->processWintabDisplayChangeEvent();
}
break;
////////////////////////////////////////////////////////////////////////
// Window events, ignored
////////////////////////////////////////////////////////////////////////

View File

@ -64,8 +64,6 @@ class GHOST_SystemWin32 : public GHOST_System {
** Time(r) functionality
***************************************************************************************/
GHOST_TUns64 millisSinceStart(__int64 ms) const;
/**
* This method converts performance counter measurements into milliseconds since the start of the
* system process.
@ -267,16 +265,6 @@ 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.
@ -368,13 +356,6 @@ 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.

View File

@ -72,7 +72,6 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
bool is_debug,
bool dialog)
: GHOST_Window(width, height, state, wantStereoVisual, false),
m_mousePresent(false),
m_tabletInRange(false),
m_inLiveResize(false),
m_system(system),
@ -82,7 +81,6 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_nPressedButtons(0),
m_customCursor(0),
m_wantAlphaBackground(alphaBackground),
m_wintab(),
m_normal_state(GHOST_kWindowStateNormal),
m_user32(NULL),
m_fpGetPointerInfoHistory(NULL),
@ -91,6 +89,10 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_parentWindowHwnd(parentwindow ? parentwindow->m_hWnd : NULL),
m_debug_context(is_debug)
{
// Initialize tablet variables
memset(&m_wintab, 0, sizeof(m_wintab));
m_tabletData = GHOST_TABLET_DATA_NONE;
// Create window
if (state != GHOST_kWindowStateFullScreen) {
RECT rect;
@ -295,24 +297,68 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_user32, "GetPointerTouchInfoHistory");
}
if ((m_wintab.handle = ::LoadLibrary("Wintab32.dll")) &&
(m_wintab.info = (GHOST_WIN32_WTInfo)::GetProcAddress(m_wintab.handle, "WTInfoA")) &&
(m_wintab.open = (GHOST_WIN32_WTOpen)::GetProcAddress(m_wintab.handle, "WTOpenA")) &&
(m_wintab.get = (GHOST_WIN32_WTGet)::GetProcAddress(m_wintab.handle, "WTGetA")) &&
(m_wintab.set = (GHOST_WIN32_WTSet)::GetProcAddress(m_wintab.handle, "WTSetA")) &&
(m_wintab.close = (GHOST_WIN32_WTClose)::GetProcAddress(m_wintab.handle, "WTClose")) &&
(m_wintab.packetsGet = (GHOST_WIN32_WTPacketsGet)::GetProcAddress(m_wintab.handle,
"WTPacketsGet")) &&
(m_wintab.queueSizeGet = (GHOST_WIN32_WTQueueSizeGet)::GetProcAddress(m_wintab.handle,
"WTQueueSizeGet")) &&
(m_wintab.queueSizeSet = (GHOST_WIN32_WTQueueSizeSet)::GetProcAddress(m_wintab.handle,
"WTQueueSizeSet")) &&
(m_wintab.enable = (GHOST_WIN32_WTEnable)::GetProcAddress(m_wintab.handle, "WTEnable")) &&
(m_wintab.overlap = (GHOST_WIN32_WTOverlap)::GetProcAddress(m_wintab.handle, "WTOverlap"))) {
initializeWintab();
setWintabEnabled(true);
}
// 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 dont 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);
}
m_wintab.info(WTI_INTERFACE, IFC_NDEVICES, &m_wintab.numDevices);
}
}
CoCreateInstance(
CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (LPVOID *)&m_Bar);
}
@ -326,12 +372,12 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
}
if (m_wintab.handle) {
setWintabEnabled(false);
if (m_wintab.close && m_wintab.context) {
m_wintab.close(m_wintab.context);
if (m_wintab.close && m_wintab.tablet) {
m_wintab.close(m_wintab.tablet);
}
FreeLibrary(m_wintab.handle);
memset(&m_wintab, 0, sizeof(m_wintab));
}
if (m_user32) {
@ -973,62 +1019,6 @@ GHOST_TSuccess GHOST_WindowWin32::hasCursorShape(GHOST_TStandardCursor cursorSha
return (getStandardCursor(cursorShape)) ? GHOST_kSuccess : GHOST_kFailure;
}
void GHOST_WindowWin32::initializeWintab()
{
/* Return if wintab library handle doesn't exist or wintab is already initialized. */
if (!m_wintab.handle || m_wintab.context) {
return;
}
/* Check if WinTab available by getting system context info. */
LOGCONTEXT lc = {0};
if (m_wintab.open && m_wintab.info && m_wintab.queueSizeGet && m_wintab.queueSizeSet &&
m_wintab.info(WTI_DEFSYSCTX, 0, &lc)) {
lc.lcPktData = PACKETDATA;
lc.lcPktMode = PACKETMODE;
lc.lcMoveMask = PACKETDATA;
lc.lcOptions |= CXO_CSRMESSAGES | CXO_MESSAGES;
/* Wacom maps y origin to the tablet's bottom. Invert to match Windows y origin mapping to the
* screen top. */
lc.lcOutExtY = -lc.lcOutExtY;
m_wintab.info(WTI_INTERFACE, IFC_NDEVICES, &m_wintab.numDevices);
updateWintabCursorInfo();
/* The Wintab spec says we must open the context disabled if we are using cursor masks. */
m_wintab.context = m_wintab.open(m_hWnd, &lc, FALSE);
/* 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 = m_wintab.queueSizeGet(m_wintab.context);
while (queueSize < maxQueue) {
int testSize = min(queueSize + 16, maxQueue);
if (m_wintab.queueSizeSet(m_wintab.context, 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. */
m_wintab.queueSizeSet(m_wintab.context, queueSize);
break;
}
}
m_wintab.pkts.resize(queueSize);
}
}
GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(
std::vector<GHOST_PointerInfoWin32> &outPointerInfo, WPARAM wParam, LPARAM lParam)
{
@ -1108,85 +1098,24 @@ GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(
}
}
if (!outPointerInfo.empty()) {
lastTabletData = outPointerInfo.back().tabletData;
}
return GHOST_kSuccess;
}
void GHOST_WindowWin32::setWintabEnabled(bool enable)
void GHOST_WindowWin32::processWin32TabletActivateEvent(WORD state)
{
if (m_wintab.enable && m_wintab.get && m_wintab.context) {
LOGCONTEXT context = {0};
if (m_wintab.get(m_wintab.context, &context)) {
if (enable && context.lcStatus & CXS_DISABLED && useTabletAPI(GHOST_kTabletWintab)) {
m_wintab.enable(m_wintab.context, true);
POINT cursorPos;
::GetCursorPos(&cursorPos);
GHOST_Rect bounds;
getClientBounds(bounds);
if (bounds.isInside(cursorPos.x, cursorPos.y)) {
setWintabOverlap(true);
}
}
else if (!enable && !(context.lcStatus & CXS_DISABLED)) {
setWintabOverlap(false);
m_wintab.enable(m_wintab.context, false);
}
}
if (!useTabletAPI(GHOST_kTabletWintab)) {
return;
}
}
void GHOST_WindowWin32::setWintabOverlap(bool overlap)
{
if (m_wintab.overlap && m_wintab.get && m_wintab.packetsGet && m_wintab.context) {
LOGCONTEXT context = {0};
if (m_wintab.enable && m_wintab.tablet) {
m_wintab.enable(m_wintab.tablet, state);
if (m_wintab.get(m_wintab.context, &context)) {
if (overlap && context.lcStatus & CXS_OBSCURED && useTabletAPI(GHOST_kTabletWintab)) {
m_wintab.overlap(m_wintab.context, true);
}
else if (!overlap && context.lcStatus & CXS_ONTOP) {
m_wintab.overlap(m_wintab.context, false);
/* If context is disabled, Windows Ink may be active and managing m_tabletInRange. Don't
* modify it. */
if (!(context.lcStatus & CXS_DISABLED)) {
processWintabLeave();
}
}
if (m_wintab.overlap && state) {
m_wintab.overlap(m_wintab.tablet, TRUE);
}
}
}
void GHOST_WindowWin32::processWintabLeave()
{
m_tabletInRange = false;
m_wintab.buttons = 0;
/* Clear the packet queue. */
m_wintab.packetsGet(m_wintab.context, m_wintab.pkts.size(), m_wintab.pkts.data());
}
void GHOST_WindowWin32::processWintabDisplayChangeEvent()
{
LOGCONTEXT lc_sys = {0}, lc_curr = {0};
if (m_wintab.info && m_wintab.get && m_wintab.set && m_wintab.info(WTI_DEFSYSCTX, 0, &lc_sys)) {
m_wintab.get(m_wintab.context, &lc_curr);
lc_curr.lcOutOrgX = lc_sys.lcOutOrgX;
lc_curr.lcOutOrgY = lc_sys.lcOutOrgY;
lc_curr.lcOutExtX = lc_sys.lcOutExtX;
lc_curr.lcOutExtY = -lc_sys.lcOutExtY;
m_wintab.set(m_wintab.context, &lc_curr);
}
}
bool GHOST_WindowWin32::useTabletAPI(GHOST_TTabletAPI api) const
{
if (m_system->getTabletAPI() == api) {
@ -1203,24 +1132,36 @@ bool GHOST_WindowWin32::useTabletAPI(GHOST_TTabletAPI api) const
}
}
void GHOST_WindowWin32::updateWintabCursorInfo()
void GHOST_WindowWin32::processWin32TabletInitEvent()
{
if (m_wintab.info && m_wintab.context) {
AXIS Pressure, Orientation[3];
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);
m_wintab.maxPressure = pressureSupport ? Pressure.axMax : 0;
if (pressureSupport)
m_wintab.maxPressure = Pressure.axMax;
else
m_wintab.maxPressure = 0;
BOOL tiltSupport = m_wintab.info(WTI_DEVICES, DVC_ORIENTATION, &Orientation);
/* Does the tablet support azimuth ([0]) and altitude ([1]). */
if (tiltSupport && Orientation[0].axResolution && Orientation[1].axResolution) {
m_wintab.maxAzimuth = Orientation[0].axMax;
m_wintab.maxAltitude = Orientation[1].axMax;
}
else {
m_wintab.maxAzimuth = m_wintab.maxAltitude = 0;
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 dont do tilt stuff */
m_wintab.maxAzimuth = m_wintab.maxAltitude = 0;
}
}
}
m_tabletData.Active = GHOST_kTabletModeNone;
}
void GHOST_WindowWin32::processWintabInfoChangeEvent(LPARAM lParam)
@ -1228,151 +1169,86 @@ void GHOST_WindowWin32::processWintabInfoChangeEvent(LPARAM lParam)
/* Update number of connected Wintab digitizers */
if (LOWORD(lParam) == WTI_INTERFACE && HIWORD(lParam) == IFC_NDEVICES) {
m_wintab.info(WTI_INTERFACE, IFC_NDEVICES, &m_wintab.numDevices);
if (useTabletAPI(GHOST_kTabletWintab)) {
setWintabEnabled(true);
}
}
}
GHOST_TButtonMask GHOST_WindowWin32::wintabMouseToGhost(UINT cursor, WORD physicalButton)
void GHOST_WindowWin32::processWin32TabletEvent(WPARAM wParam, LPARAM lParam)
{
const WORD numButtons = 32;
BYTE logicalButtons[numButtons] = {0};
BYTE systemButtons[numButtons] = {0};
m_wintab.info(WTI_CURSORS + cursor, CSR_BUTTONMAP, &logicalButtons);
m_wintab.info(WTI_CURSORS + cursor, CSR_SYSBTNMAP, &systemButtons);
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;
}
}
GHOST_TSuccess GHOST_WindowWin32::getWintabInfo(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
{
if (!(useTabletAPI(GHOST_kTabletWintab) && m_wintab.packetsGet && m_wintab.context)) {
return GHOST_kFailure;
if (!useTabletAPI(GHOST_kTabletWintab)) {
return;
}
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem();
const int numPackets = m_wintab.packetsGet(
m_wintab.context, m_wintab.pkts.size(), m_wintab.pkts.data());
outWintabInfo.resize(numPackets);
for (int i = 0; i < numPackets; i++) {
PACKET pkt = m_wintab.pkts[i];
GHOST_WintabInfoWin32 &out = outWintabInfo[i];
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_wintab.maxPressure > 0) {
out.tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_wintab.maxPressure;
}
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. */
out.tabletData.Xtilt = sin(azmRad) * vecLen;
out.tabletData.Ytilt = (float)(sin(M_PI / 2.0 - azmRad) * vecLen);
}
/* 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_wintab.buttons ^ pkt.pkButtons;
if (buttonsChanged) {
/* Find the index for the changed button from the button map. */
WORD physicalButton = 0;
for (DWORD diff = (unsigned)buttonsChanged >> 1; diff > 0; diff = (unsigned)diff >> 1) {
physicalButton++;
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;
}
out.button = wintabMouseToGhost(pkt.pkCursor, physicalButton);
if (out.button != GHOST_kButtonMaskNone) {
if (buttonsChanged & pkt.pkButtons) {
out.type = GHOST_kEventButtonDown;
}
else {
out.type = GHOST_kEventButtonUp;
}
if (m_wintab.maxPressure > 0) {
m_tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_wintab.maxPressure;
}
else {
m_tabletData.Pressure = 1.0f;
}
/* Only update handled button, in case multiple button events arrived simultaneously. */
m_wintab.buttons ^= 1 << physicalButton;
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;
}
}
out.time = system->tickCountToMillis(pkt.pkTime);
}
if (!outWintabInfo.empty()) {
lastTabletData = outWintabInfo.back().tabletData;
}
return GHOST_kSuccess;
}
GHOST_TabletData GHOST_WindowWin32::getLastTabletData()
void GHOST_WindowWin32::bringTabletContextToFront()
{
return lastTabletData;
if (!useTabletAPI(GHOST_kTabletWintab)) {
return;
}
if (m_wintab.overlap && m_wintab.tablet) {
m_wintab.overlap(m_wintab.tablet, TRUE);
}
}
GHOST_TUns16 GHOST_WindowWin32::getDPIHint()

View File

@ -34,14 +34,12 @@
# include "GHOST_ImeWin32.h"
#endif
#include <queue>
#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 | PK_X | PK_Y | PK_TIME)
#define PACKETMODE 0
#define PACKETDATA (PK_BUTTONS | PK_NORMAL_PRESSURE | PK_ORIENTATION | PK_CURSOR)
#define PACKETMODE PK_BUTTONS
#include <pktdef.h>
class GHOST_SystemWin32;
@ -49,13 +47,9 @@ class GHOST_DropTargetWin32;
// 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_WTPacket)(HCTX, UINT, LPVOID);
typedef BOOL(API *GHOST_WIN32_WTEnable)(HCTX, BOOL);
typedef BOOL(API *GHOST_WIN32_WTOverlap)(HCTX, BOOL);
@ -236,14 +230,7 @@ struct GHOST_PointerInfoWin32 {
GHOST_TButtonMask buttonMask;
POINT pixelLocation;
GHOST_TUns64 time;
GHOST_TabletData tabletData;
};
struct GHOST_WintabInfoWin32 {
GHOST_TInt32 x, y;
GHOST_TEventType type;
GHOST_TButtonMask button;
GHOST_TUns64 time;
GHOST_TabletData tabletData;
};
@ -437,6 +424,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.
@ -454,51 +446,16 @@ class GHOST_WindowWin32 : public GHOST_Window {
WPARAM wParam,
LPARAM lParam);
/**
* Enables or disables Wintab context.
* \param enable: Whether the context should be enabled.
*/
void setWintabEnabled(bool enable);
/**
* Changes Wintab context overlap.
* \param overlap: Whether context should be brought to top of overlap order.
*/
void setWintabOverlap(bool overlap);
/**
* Resets Wintab state.
*/
void processWintabLeave();
/**
* Handle Wintab coordinate changes when DisplayChange events occur.
*/
void processWintabDisplayChangeEvent();
/**
* Updates cached Wintab properties for current cursor.
*/
void updateWintabCursorInfo();
/**
* Handle Wintab info changes such as change in number of connected tablets.
* \param lParam: LPARAM of the event.
*/
void processWintabInfoChangeEvent(LPARAM lParam);
/**
* Translate Wintab packets into GHOST_WintabInfoWin32 structs.
* \param outWintabInfo: Storage to return resulting GHOST_WintabInfoWin32 structs.
* \return Success if able to read packets, even if there are none.
*/
GHOST_TSuccess getWintabInfo(std::vector<GHOST_WintabInfoWin32> &outWintabInfo);
/**
* Get the most recent tablet data.
* \return Most recent tablet data.
*/
GHOST_TabletData getLastTabletData();
void processWin32TabletActivateEvent(WORD state);
void processWin32TabletInitEvent();
void processWin32TabletEvent(WPARAM wParam, LPARAM lParam);
void bringTabletContextToFront();
GHOST_TSuccess beginFullScreen() const
{
@ -512,9 +469,6 @@ class GHOST_WindowWin32 : public GHOST_Window {
GHOST_TUns16 getDPIHint() override;
/** Whether the mouse is either over or captured by the window. */
bool m_mousePresent;
/** Whether a tablet stylus is being tracked. */
bool m_tabletInRange;
@ -598,51 +552,29 @@ 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;
HMODULE handle;
/** API functions */
GHOST_WIN32_WTInfo info = NULL;
GHOST_WIN32_WTGet get = NULL;
GHOST_WIN32_WTSet set = NULL;
GHOST_WIN32_WTOpen open = NULL;
GHOST_WIN32_WTClose close = NULL;
GHOST_WIN32_WTPacketsGet packetsGet = NULL;
GHOST_WIN32_WTQueueSizeGet queueSizeGet = NULL;
GHOST_WIN32_WTQueueSizeSet queueSizeSet = NULL;
GHOST_WIN32_WTEnable enable = NULL;
GHOST_WIN32_WTOverlap overlap = NULL;
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 context = NULL;
/** Number of connected Wintab digitizers. */
UINT numDevices = 0;
/** Pressed button map. */
GHOST_TUns8 buttons = 0;
LONG maxPressure = 0;
LONG maxAzimuth = 0, maxAltitude = 0;
/** Reusable buffer to read in Wintab Packets. */
std::vector<PACKET> pkts;
/** Stores the Tablet context if detected Tablet features using WinTab.dll */
HCTX tablet;
LONG maxPressure;
LONG maxAzimuth, maxAltitude;
UINT numDevices;
} m_wintab;
/** Most recent tablet data. */
GHOST_TabletData lastTabletData = GHOST_TABLET_DATA_NONE;
/**
* Wintab setup.
*/
void initializeWintab();
/**
* 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 wintabMouseToGhost(UINT cursor, WORD physicalButton);
GHOST_TWindowState m_normal_state;
/** user32 dll handle*/

View File

@ -5824,8 +5824,10 @@ static void rna_def_userdef_input(BlenderRNA *brna)
prop = RNA_def_property(srna, "tablet_api", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, tablet_api);
RNA_def_property_ui_text(
prop, "Tablet API", "Select the tablet API to use for pressure sensitivity");
RNA_def_property_ui_text(prop,
"Tablet API",
"Select the tablet API to use for pressure sensitivity (may require "
"restarting Blender for changes to take effect)");
RNA_def_property_update(prop, 0, "rna_userdef_tablet_api_update");
# ifdef WITH_INPUT_NDOF