Windows: refactor GHOST wintab handling.

This is backporting a change from 2.8, which may help solve crashes when
activating a window. Previously bringTabletContextToFront() would call
tablet API functions with NULL tablet, which may crash on some drivers.

Ref T60811.
This commit is contained in:
Brecht Van Lommel 2019-01-14 19:12:02 +01:00
parent fccf506ed7
commit 6fdc688cd0
2 changed files with 133 additions and 141 deletions

@ -79,15 +79,17 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_nPressedButtons(0), m_nPressedButtons(0),
m_customCursor(0), m_customCursor(0),
m_wantAlphaBackground(alphaBackground), m_wantAlphaBackground(alphaBackground),
m_wintab(NULL),
m_tabletData(NULL),
m_tablet(0),
m_maxPressure(0),
m_normal_state(GHOST_kWindowStateNormal), m_normal_state(GHOST_kWindowStateNormal),
m_user32(NULL), m_user32(NULL),
m_parentWindowHwnd(parentwindowhwnd), m_parentWindowHwnd(parentwindowhwnd),
m_debug_context(is_debug) m_debug_context(is_debug)
{ {
// Initialize tablet variables
memset(&m_wintab, 0, sizeof(m_wintab));
memset(&m_tabletData, 0, sizeof(m_tabletData));
m_tabletData.Active = GHOST_kTabletModeNone;
// Create window
if (state != GHOST_kWindowStateFullScreen) { if (state != GHOST_kWindowStateFullScreen) {
RECT rect; RECT rect;
MONITORINFO monitor; MONITORINFO monitor;
@ -274,16 +276,22 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
RegisterRawInputDevices(&device, 1, sizeof(device)); RegisterRawInputDevices(&device, 1, sizeof(device));
} }
m_wintab = ::LoadLibrary("Wintab32.dll"); // Initialize Wintab
if (m_wintab) { m_wintab.handle = ::LoadLibrary("Wintab32.dll");
GHOST_WIN32_WTInfo fpWTInfo = (GHOST_WIN32_WTInfo) ::GetProcAddress(m_wintab, "WTInfoA"); if (m_wintab.handle) {
GHOST_WIN32_WTOpen fpWTOpen = (GHOST_WIN32_WTOpen) ::GetProcAddress(m_wintab, "WTOpenA"); // 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. // Let's see if we can initialize tablet here.
// Check if WinTab available by getting system context info. // Check if WinTab available by getting system context info.
LOGCONTEXT lc = { 0 }; LOGCONTEXT lc = { 0 };
lc.lcOptions |= CXO_SYSTEM; lc.lcOptions |= CXO_SYSTEM;
if (fpWTInfo && fpWTInfo(WTI_DEFSYSCTX, 0, &lc)) { if (m_wintab.open && m_wintab.info && m_wintab.info(WTI_DEFSYSCTX, 0, &lc)) {
// Now init the tablet // Now init the tablet
/* The maximum tablet size, pressure and orientation (tilt) */ /* The maximum tablet size, pressure and orientation (tilt) */
AXIS TabletX, TabletY, Pressure, Orientation[3]; AXIS TabletX, TabletY, Pressure, Orientation[3];
@ -297,42 +305,34 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
lc.lcMoveMask = PACKETDATA; lc.lcMoveMask = PACKETDATA;
/* Set the entire tablet as active */ /* Set the entire tablet as active */
fpWTInfo(WTI_DEVICES, DVC_X, &TabletX); m_wintab.info(WTI_DEVICES, DVC_X, &TabletX);
fpWTInfo(WTI_DEVICES, DVC_Y, &TabletY); m_wintab.info(WTI_DEVICES, DVC_Y, &TabletY);
/* get the max pressure, to divide into a float */ /* get the max pressure, to divide into a float */
BOOL pressureSupport = fpWTInfo(WTI_DEVICES, DVC_NPRESSURE, &Pressure); BOOL pressureSupport = m_wintab.info(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
if (pressureSupport) if (pressureSupport)
m_maxPressure = Pressure.axMax; m_wintab.maxPressure = Pressure.axMax;
else else
m_maxPressure = 0; m_wintab.maxPressure = 0;
/* get the max tilt axes, to divide into floats */ /* get the max tilt axes, to divide into floats */
BOOL tiltSupport = fpWTInfo(WTI_DEVICES, DVC_ORIENTATION, &Orientation); BOOL tiltSupport = m_wintab.info(WTI_DEVICES, DVC_ORIENTATION, &Orientation);
if (tiltSupport) { if (tiltSupport) {
/* does the tablet support azimuth ([0]) and altitude ([1]) */ /* does the tablet support azimuth ([0]) and altitude ([1]) */
if (Orientation[0].axResolution && Orientation[1].axResolution) { if (Orientation[0].axResolution && Orientation[1].axResolution) {
/* all this assumes the minimum is 0 */ /* all this assumes the minimum is 0 */
m_maxAzimuth = Orientation[0].axMax; m_wintab.maxAzimuth = Orientation[0].axMax;
m_maxAltitude = Orientation[1].axMax; m_wintab.maxAltitude = Orientation[1].axMax;
} }
else { /* no so dont do tilt stuff */ else { /* no so dont do tilt stuff */
m_maxAzimuth = m_maxAltitude = 0; m_wintab.maxAzimuth = m_wintab.maxAltitude = 0;
} }
} }
if (fpWTOpen) { // The Wintab spec says we must open the context disabled if we are using cursor masks.
// 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);
m_tablet = fpWTOpen(m_hWnd, &lc, FALSE); if (m_wintab.enable && m_wintab.tablet) {
if (m_tablet) { m_wintab.enable(m_wintab.tablet, TRUE);
m_tabletData = new GHOST_TabletData();
m_tabletData->Active = GHOST_kTabletModeNone;
}
GHOST_WIN32_WTEnable fpWTEnable = (GHOST_WIN32_WTEnable) ::GetProcAddress(m_wintab, "WTEnable");
if (fpWTEnable) {
fpWTEnable(m_tablet, TRUE);
}
} }
} }
} }
@ -347,14 +347,13 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
m_Bar->Release(); m_Bar->Release();
} }
if (m_wintab) { if (m_wintab.handle) {
GHOST_WIN32_WTClose fpWTClose = (GHOST_WIN32_WTClose) ::GetProcAddress(m_wintab, "WTClose"); if (m_wintab.close && m_wintab.tablet) {
if (fpWTClose) { m_wintab.close(m_wintab.tablet);
if (m_tablet)
fpWTClose(m_tablet);
delete m_tabletData;
m_tabletData = NULL;
} }
FreeLibrary(m_wintab.handle);
memset(&m_wintab, 0, sizeof(m_wintab));
} }
if (m_customCursor) { if (m_customCursor) {
@ -883,118 +882,103 @@ GHOST_TSuccess GHOST_WindowWin32::setWindowCursorShape(GHOST_TStandardCursor cur
void GHOST_WindowWin32::processWin32TabletActivateEvent(WORD state) void GHOST_WindowWin32::processWin32TabletActivateEvent(WORD state)
{ {
if (!m_tablet) { if (m_wintab.enable && m_wintab.tablet) {
return; m_wintab.enable(m_wintab.tablet, state);
}
GHOST_WIN32_WTEnable fpWTEnable = (GHOST_WIN32_WTEnable) ::GetProcAddress(m_wintab, "WTEnable"); if (m_wintab.overlap && state) {
GHOST_WIN32_WTOverlap fpWTOverlap = (GHOST_WIN32_WTOverlap) ::GetProcAddress(m_wintab, "WTOverlap"); m_wintab.overlap(m_wintab.tablet, TRUE);
if (fpWTEnable) {
fpWTEnable(m_tablet, state);
if (fpWTOverlap && state) {
fpWTOverlap(m_tablet, TRUE);
} }
} }
} }
void GHOST_WindowWin32::processWin32TabletInitEvent() void GHOST_WindowWin32::processWin32TabletInitEvent()
{ {
if (m_wintab && m_tabletData) { // Let's see if we can initialize tablet here
GHOST_WIN32_WTInfo fpWTInfo = (GHOST_WIN32_WTInfo) ::GetProcAddress(m_wintab, "WTInfoA"); if (m_wintab.info && m_wintab.tablet) {
AXIS Pressure, Orientation[3]; /* The maximum tablet size */
// let's see if we can initialize tablet here BOOL pressureSupport = m_wintab.info(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
/* check if WinTab available. */ if (pressureSupport)
if (fpWTInfo) { m_wintab.maxPressure = Pressure.axMax;
AXIS Pressure, Orientation[3]; /* The maximum tablet size */ else
m_wintab.maxPressure = 0;
BOOL pressureSupport = fpWTInfo(WTI_DEVICES, DVC_NPRESSURE, &Pressure); BOOL tiltSupport = m_wintab.info(WTI_DEVICES, DVC_ORIENTATION, &Orientation);
if (pressureSupport) if (tiltSupport) {
m_maxPressure = Pressure.axMax; /* does the tablet support azimuth ([0]) and altitude ([1]) */
else if (Orientation[0].axResolution && Orientation[1].axResolution) {
m_maxPressure = 0; m_wintab.maxAzimuth = Orientation[0].axMax;
m_wintab.maxAltitude = Orientation[1].axMax;
BOOL tiltSupport = fpWTInfo(WTI_DEVICES, DVC_ORIENTATION, &Orientation); }
if (tiltSupport) { else { /* no so dont do tilt stuff */
/* does the tablet support azimuth ([0]) and altitude ([1]) */ m_wintab.maxAzimuth = m_wintab.maxAltitude = 0;
if (Orientation[0].axResolution && Orientation[1].axResolution) {
m_maxAzimuth = Orientation[0].axMax;
m_maxAltitude = Orientation[1].axMax;
}
else { /* no so dont do tilt stuff */
m_maxAzimuth = m_maxAltitude = 0;
}
} }
m_tabletData->Active = GHOST_kTabletModeNone;
} }
m_tabletData.Active = GHOST_kTabletModeNone;
} }
} }
void GHOST_WindowWin32::processWin32TabletEvent(WPARAM wParam, LPARAM lParam) void GHOST_WindowWin32::processWin32TabletEvent(WPARAM wParam, LPARAM lParam)
{ {
PACKET pkt; if (m_wintab.packet && m_wintab.tablet) {
if (m_wintab) { PACKET pkt;
GHOST_WIN32_WTPacket fpWTPacket = (GHOST_WIN32_WTPacket) ::GetProcAddress(m_wintab, "WTPacket"); if (m_wintab.packet((HCTX)lParam, wParam, &pkt)) {
if (fpWTPacket) { switch (pkt.pkCursor % 3) { /* % 3 for multiple devices ("DualTrack") */
if (fpWTPacket((HCTX)lParam, wParam, &pkt)) { case 0:
if (m_tabletData) { m_tabletData.Active = GHOST_kTabletModeNone; /* puck - not yet supported */
switch (pkt.pkCursor % 3) { /* % 3 for multiple devices ("DualTrack") */ break;
case 0: case 1:
m_tabletData->Active = GHOST_kTabletModeNone; /* puck - not yet supported */ m_tabletData.Active = GHOST_kTabletModeStylus; /* stylus */
break; break;
case 1: case 2:
m_tabletData->Active = GHOST_kTabletModeStylus; /* stylus */ m_tabletData.Active = GHOST_kTabletModeEraser; /* eraser */
break; break;
case 2: }
m_tabletData->Active = GHOST_kTabletModeEraser; /* eraser */
break;
}
if (m_maxPressure > 0) {
m_tabletData->Pressure = (float)pkt.pkNormalPressure / (float)m_maxPressure;
}
else {
m_tabletData->Pressure = 1.0f;
}
if ((m_maxAzimuth > 0) && (m_maxAltitude > 0)) { if (m_wintab.maxPressure > 0) {
ORIENTATION ort = pkt.pkOrientation; m_tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_wintab.maxPressure;
float vecLen; }
float altRad, azmRad; /* in radians */ else {
m_tabletData.Pressure = 1.0f;
}
/* if ((m_wintab.maxAzimuth > 0) && (m_wintab.maxAltitude > 0)) {
* from the wintab spec: ORIENTATION ort = pkt.pkOrientation;
* orAzimuth Specifies the clockwise rotation of the float vecLen;
* cursor about the z axis through a full circular range. float altRad, azmRad; /* in radians */
*
* 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); * from the wintab spec:
azmRad = (float)(((float)ort.orAzimuth / (float)m_maxAzimuth) * M_PI * 2.0); * 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.
*/
/* find length of the stylus' projected vector on the XY plane */ /* convert raw fixed point data to radians */
vecLen = cos(altRad); 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);
/* from there calculate X and Y components based on azimuth */ /* find length of the stylus' projected vector on the XY plane */
m_tabletData->Xtilt = sin(azmRad) * vecLen; vecLen = cos(altRad);
m_tabletData->Ytilt = (float)(sin(M_PI / 2.0 - azmRad) * vecLen);
} /* from there calculate X and Y components based on azimuth */
else { m_tabletData.Xtilt = sin(azmRad) * vecLen;
m_tabletData->Xtilt = 0.0f; m_tabletData.Ytilt = (float)(sin(M_PI / 2.0 - azmRad) * vecLen);
m_tabletData->Ytilt = 0.0f;
} }
} else {
m_tabletData.Xtilt = 0.0f;
m_tabletData.Ytilt = 0.0f;
} }
} }
} }
@ -1002,11 +986,8 @@ void GHOST_WindowWin32::processWin32TabletEvent(WPARAM wParam, LPARAM lParam)
void GHOST_WindowWin32::bringTabletContextToFront() void GHOST_WindowWin32::bringTabletContextToFront()
{ {
if (m_wintab) { if (m_wintab.overlap && m_wintab.tablet) {
GHOST_WIN32_WTOverlap fpWTOverlap = (GHOST_WIN32_WTOverlap) ::GetProcAddress(m_wintab, "WTOverlap"); m_wintab.overlap(m_wintab.tablet, TRUE);
if (fpWTOverlap) {
fpWTOverlap(m_tablet, TRUE);
}
} }
} }

@ -242,7 +242,7 @@ public:
const GHOST_TabletData *GetTabletData() const GHOST_TabletData *GetTabletData()
{ {
return m_tabletData; return &m_tabletData;
} }
void processWin32TabletActivateEvent(WORD state); void processWin32TabletActivateEvent(WORD state);
@ -343,16 +343,27 @@ private:
static const wchar_t *s_windowClassName; static const wchar_t *s_windowClassName;
static const int s_maxTitleLength; static const int s_maxTitleLength;
/** WinTab dll handle */
HMODULE m_wintab;
/** Tablet data for GHOST */ /** Tablet data for GHOST */
GHOST_TabletData *m_tabletData; GHOST_TabletData m_tabletData;
/** Stores the Tablet context if detected Tablet features using WinTab.dll */ /* Wintab API */
HCTX m_tablet; struct {
LONG m_maxPressure; /** WinTab dll handle */
LONG m_maxAzimuth, m_maxAltitude; HMODULE handle;
/** 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;
GHOST_TWindowState m_normal_state; GHOST_TWindowState m_normal_state;