Revert "Windows: support high resolution tablet pen events for Wintab"

This reverts commit 1a3928f33c and 1a3928f3. This is not working stable
with some Wintab implementations, so reverting for now. This leaves only
the Windows Ink changes for 2.83.
This commit is contained in:
Brecht Van Lommel 2020-04-14 18:01:14 +02:00
parent 65f674b570
commit e90d8422d0
Notes: blender-bot 2023-02-14 10:21:10 +01:00
Referenced by commit abbdfc8bf7, Revert "Revert "Windows: support high resolution tablet pen events for Wintab""
Referenced by issue #75723, Tapping with a Wacom Intuos pen tip to left-click is faltering
Referenced by issue #75583, Cannot navigate in Sculpt mode with Wacom Intuos Pro
Referenced by issue #75566, Wacom tablet's side switches don't work
5 changed files with 208 additions and 535 deletions

View File

@ -238,7 +238,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

@ -938,100 +938,15 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type,
window->updateMouseCapture(MouseReleased);
}
/* Check for active Wintab mouse emulation in addition to a tablet in range because a proximity
* leave event might have fired before the Windows mouse up event, thus there are still tablet
* events to grab. The described behavior was observed in a Wacom Bamboo CTE-450.
*/
if (window->m_tabletInRange || window->wintabSysButPressed()) {
if (window->useTabletAPI(GHOST_kTabletWintab) && processWintabEvents(type, window)) {
// Wintab processing only handles in-contact events.
return NULL;
}
else if (window->useTabletAPI(GHOST_kTabletNative)) {
if (window->m_tabletInRange) {
if (window->useTabletAPI(GHOST_kTabletNative)) {
// Win32 Pointer processing handles input while in-range and in-contact events.
return NULL;
}
// If using Wintab and this was a button down event but no button event was queued while
// processing Wintab packets, fall through to create a button event.
}
return new GHOST_EventButton(
system->getMilliSeconds(), type, window, mask, GHOST_TABLET_DATA_NONE);
}
GHOST_TSuccess GHOST_SystemWin32::processWintabEvents(GHOST_TEventType type,
GHOST_WindowWin32 *window)
{
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
/* Only process Wintab packets if we can correlate them to a Window's mouse button event. When a
* button event associated to a mouse button by Wintab occurs outside of WM_*BUTTON events,
* there's no way to tell if other simultaneously pressed non-mouse mapped buttons are associated
* to a modifier key (shift, alt, ctrl) or a system event (scroll, etc.) and thus it is not
* possible to determine if a mouse click event should occur.
*/
if (!window->getMousePressed() && !window->wintabSysButPressed()) {
return GHOST_kFailure;
}
std::vector<GHOST_WintabInfoWin32> wintabInfo;
if (!window->getWintabInfo(wintabInfo)) {
return GHOST_kFailure;
}
auto wtiIter = wintabInfo.begin();
/* We only process events that correlate to a mouse button events, so there may exist Wintab
* button down events that were instead mapped to e.g. scroll still in the queue. We need to
* skip those and find the last button down mapped to mouse buttons.
*/
if (!window->wintabSysButPressed()) {
for (auto it = wtiIter; it != wintabInfo.end(); it++) {
if (it->type == GHOST_kEventButtonDown) {
wtiIter = it;
}
}
}
for (; wtiIter != wintabInfo.end(); wtiIter++) {
auto info = *wtiIter;
switch (info.type) {
case GHOST_kEventButtonDown: {
/* While changing windows with a tablet, Window's mouse button events normally occur before
* tablet proximity events, so a button up event can't be differentiated as occurring from
* a Wintab tablet or a normal mouse and a Ghost button event will always be generated.
*
* If we were called during a button down event create a ghost button down event, otherwise
* don't duplicate the prior button down as it interrupts drawing immediately after
* changing a window.
*/
if (type == GHOST_kEventButtonDown) {
// Move cursor to point of contact because GHOST_EventButton does not include position.
system->pushEvent(new GHOST_EventCursor(
info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
system->pushEvent(
new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
}
window->updateWintabSysBut(MousePressed);
break;
}
case GHOST_kEventCursorMove:
system->pushEvent(new GHOST_EventCursor(
info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
break;
case GHOST_kEventButtonUp:
system->pushEvent(
new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
window->updateWintabSysBut(MouseReleased);
break;
default:
break;
}
}
return GHOST_kSuccess;
system->getMilliSeconds(), type, window, mask, window->getTabletData());
}
void GHOST_SystemWin32::processPointerEvents(
@ -1119,19 +1034,13 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
GHOST_TInt32 x_screen, y_screen;
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
if (window->m_tabletInRange || window->wintabSysButPressed()) {
if (window->useTabletAPI(GHOST_kTabletWintab) &&
processWintabEvents(GHOST_kEventCursorMove, window)) {
return NULL;
}
else if (window->useTabletAPI(GHOST_kTabletNative)) {
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 using Wintab but no button event is currently active, fall through to default handling
}
system->getCursorPosition(x_screen, y_screen);
@ -1164,7 +1073,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
window,
x_screen + x_accum,
y_screen + y_accum,
GHOST_TABLET_DATA_NONE);
window->getTabletData());
}
}
else {
@ -1173,7 +1082,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
window,
x_screen,
y_screen,
GHOST_TABLET_DATA_NONE);
window->getTabletData());
}
return NULL;
}
@ -1284,6 +1193,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);
@ -1311,19 +1221,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();
GHOST_WindowWin32 *activeWindow = (GHOST_WindowWin32 *)wm->getActiveWindow();
for (GHOST_IWindow *win : wm->getWindows()) {
GHOST_WindowWin32 *windowsWindow = (GHOST_WindowWin32 *)win;
windowsWindow->updateWintab(windowsWindow == activeWindow);
}
}
void GHOST_SystemWin32::processMinMaxInfo(MINMAXINFO *minmax)
{
minmax->ptMinTrackSize.x = 320;
@ -1559,17 +1456,15 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
}
break;
////////////////////////////////////////////////////////////////////////
// Wintab events, processed
// Tablet events, processed
////////////////////////////////////////////////////////////////////////
case WT_INFOCHANGE: {
window->processWintabInfoChangeEvent(lParam);
case WT_PACKET:
window->processWin32TabletEvent(wParam, lParam);
break;
}
case WT_PROXIMITY: {
bool inRange = LOWORD(lParam);
window->processWintabProximityEvent(inRange);
case WT_CSRCHANGE:
case WT_PROXIMITY:
window->processWin32TabletInitEvent();
break;
}
////////////////////////////////////////////////////////////////////////
// Pointer events, processed
////////////////////////////////////////////////////////////////////////
@ -1708,9 +1603,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->updateWintab(LOWORD(wParam) != WA_INACTIVE);
window->processWin32TabletActivateEvent(GET_WM_ACTIVATE_STATE(wParam, lParam));
lResult = ::DefWindowProc(hwnd, msg, wParam, lParam);
break;
}
@ -1768,11 +1661,6 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
else {
event = processWindowEvent(GHOST_kEventWindowSize, window);
}
if (msg == WM_SIZE && wParam == SIZE_MINIMIZED) {
window->updateWintab(false);
}
break;
case WM_CAPTURECHANGED:
window->lostMouseCapture();
@ -1823,12 +1711,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

@ -266,16 +266,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.

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_tabletInRange(false),
m_inLiveResize(false),
m_system(system),
m_hDC(0),
@ -81,15 +80,19 @@ 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),
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));
m_tabletData = GHOST_TABLET_DATA_NONE;
// Create window
if (state != GHOST_kWindowStateFullScreen) {
RECT rect;
@ -294,25 +297,66 @@ 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();
// Determine which tablet API to use and enable it.
updateWintab(true);
}
// Initialize Wintab
m_wintab.handle = ::LoadLibrary("Wintab32.dll");
if (m_wintab.handle) {
// 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);
}
}
}
CoCreateInstance(
CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (LPVOID *)&m_Bar);
}
@ -326,8 +370,8 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
}
if (m_wintab.handle) {
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);
@ -776,27 +820,6 @@ bool GHOST_WindowWin32::getMousePressed() const
return m_nPressedButtons;
}
bool GHOST_WindowWin32::wintabSysButPressed() const
{
return m_wintab.numSysButtons;
}
void GHOST_WindowWin32::updateWintabSysBut(GHOST_MouseCaptureEventWin32 event)
{
switch (event) {
case MousePressed:
m_wintab.numSysButtons++;
break;
case MouseReleased:
if (m_wintab.numSysButtons)
m_wintab.numSysButtons--;
break;
case OperatorGrab:
case OperatorUngrab:
break;
}
}
HCURSOR GHOST_WindowWin32::getStandardCursor(GHOST_TStandardCursor shape) const
{
// Convert GHOST cursor to Windows OEM cursor
@ -1000,103 +1023,6 @@ GHOST_TSuccess GHOST_WindowWin32::hasCursorShape(GHOST_TStandardCursor cursorSha
return (getStandardCursor(cursorShape)) ? GHOST_kSuccess : GHOST_kFailure;
}
void GHOST_WindowWin32::updateWintab(bool active)
{
if (m_wintab.enable && m_wintab.overlap && m_wintab.context) {
bool useWintab = useTabletAPI(GHOST_kTabletWintab);
bool enable = active && useWintab;
// Disabling context while the Window is not minimized can cause issues on receiving Wintab
// input while changing a window for some drivers, so only disable if either Wintab had been
// disabled or the window is minimized.
m_wintab.enable(m_wintab.context, useWintab && !::IsIconic(m_hWnd));
m_wintab.overlap(m_wintab.context, enable);
if (!enable) {
// WT_PROXIMITY event doesn't occur unless tablet's cursor leaves the proximity while the
// window is active.
m_tabletInRange = false;
m_wintab.numSysButtons = 0;
m_wintab.sysButtonsPressed = 0;
}
}
}
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;
}
// Let's see if we can initialize tablet here.
// 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)) {
// Now init the tablet
/* The pressure and orientation (tilt) */
AXIS Pressure, Orientation[3];
// Open a Wintab context
// Open the context
lc.lcPktData = PACKETDATA;
lc.lcPktMode = PACKETMODE;
lc.lcMoveMask = PACKETDATA;
// 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);
/* get the max pressure, to divide into a float */
BOOL pressureSupport = m_wintab.info(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
m_wintab.maxPressure = pressureSupport ? Pressure.axMax : 0;
/* get the max tilt axes, to divide into floats */
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) {
/* 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.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.
*/
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)
{
@ -1179,20 +1105,28 @@ GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(
return GHOST_kSuccess;
}
void GHOST_WindowWin32::processWintabDisplayChangeEvent()
void GHOST_WindowWin32::setTabletData(GHOST_TabletData *pTabletData)
{
LOGCONTEXT lc_sys = {0}, lc_curr = {0};
if (pTabletData) {
m_tabletData = *pTabletData;
}
else {
m_tabletData = GHOST_TABLET_DATA_NONE;
}
}
if (m_wintab.info && m_wintab.get && m_wintab.set && m_wintab.info(WTI_DEFSYSCTX, 0, &lc_sys)) {
void GHOST_WindowWin32::processWin32TabletActivateEvent(WORD state)
{
if (!useTabletAPI(GHOST_kTabletWintab)) {
return;
}
m_wintab.get(m_wintab.context, &lc_curr);
if (m_wintab.enable && m_wintab.tablet) {
m_wintab.enable(m_wintab.tablet, state);
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);
if (m_wintab.overlap && state) {
m_wintab.overlap(m_wintab.tablet, TRUE);
}
}
}
@ -1202,7 +1136,7 @@ bool GHOST_WindowWin32::useTabletAPI(GHOST_TTabletAPI api) const
return true;
}
else if (m_system->getTabletAPI() == GHOST_kTabletAutomatic) {
if (m_wintab.numDevices)
if (m_wintab.tablet)
return api == GHOST_kTabletWintab;
else
return api == GHOST_kTabletNative;
@ -1212,180 +1146,115 @@ bool GHOST_WindowWin32::useTabletAPI(GHOST_TTabletAPI api) const
}
}
void GHOST_WindowWin32::processWintabProximityEvent(bool inRange)
void GHOST_WindowWin32::processWin32TabletInitEvent()
{
if (!useTabletAPI(GHOST_kTabletWintab)) {
return;
}
// Let's see if we can initialize tablet here
if (m_wintab.info && m_wintab.context) {
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 { /* no so dont do tilt stuff */
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_tabletInRange = inRange;
m_tabletData.Active = GHOST_kTabletModeNone;
}
void GHOST_WindowWin32::processWintabInfoChangeEvent(LPARAM lParam)
{
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem();
// 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);
updateWintab((GHOST_WindowWin32 *)system->getWindowManager()->getActiveWindow() == this);
}
}
GHOST_TSuccess GHOST_WindowWin32::wintabMouseToGhost(UINT cursor,
DWORD physicalButton,
GHOST_TButtonMask &ghostButton)
{
const DWORD 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_kFailure;
}
BYTE lb = logicalButtons[physicalButton];
if (lb >= numButtons) {
return GHOST_kFailure;
}
switch (systemButtons[lb]) {
case SBN_LCLICK:
ghostButton = GHOST_kButtonMaskLeft;
return GHOST_kSuccess;
case SBN_RCLICK:
ghostButton = GHOST_kButtonMaskRight;
return GHOST_kSuccess;
case SBN_MCLICK:
ghostButton = GHOST_kButtonMaskMiddle;
return GHOST_kSuccess;
default:
return GHOST_kFailure;
}
}
GHOST_TSuccess GHOST_WindowWin32::getWintabInfo(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
void GHOST_WindowWin32::processWin32TabletEvent(WPARAM wParam, LPARAM lParam)
{
if (!useTabletAPI(GHOST_kTabletWintab)) {
return GHOST_kFailure;
return;
}
if (!(m_wintab.packetsGet && m_wintab.context)) {
return GHOST_kFailure;
}
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;
}
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_TabletData tabletData = GHOST_TABLET_DATA_NONE;
switch (pkt.pkCursor % 3) { /* % 3 for multiple devices ("DualTrack") */
case 0:
tabletData.Active = GHOST_kTabletModeNone; /* puck - not yet supported */
break;
case 1:
tabletData.Active = GHOST_kTabletModeStylus; /* stylus */
break;
case 2:
tabletData.Active = GHOST_kTabletModeEraser; /* eraser */
break;
}
if (m_wintab.maxPressure > 0) {
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 */
tabletData.Xtilt = sin(azmRad) * vecLen;
tabletData.Ytilt = (float)(sin(M_PI / 2.0 - azmRad) * vecLen);
}
outWintabInfo[i].x = pkt.pkX;
outWintabInfo[i].y = pkt.pkY;
// Some Wintab libraries don't handle relative button input correctly, so we track button
// presses manually.
DWORD buttonsChanged = m_wintab.sysButtonsPressed ^ pkt.pkButtons;
// Find the index for the changed button from the button map.
DWORD physicalButton = 0;
for (DWORD diff = (unsigned)buttonsChanged >> 1; diff > 0; diff = (unsigned)diff >> 1) {
physicalButton++;
}
if (buttonsChanged &&
wintabMouseToGhost(pkt.pkCursor, physicalButton, outWintabInfo[i].button)) {
if (buttonsChanged & pkt.pkButtons) {
outWintabInfo[i].type = GHOST_kEventButtonDown;
if (m_wintab.maxPressure > 0) {
m_tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_wintab.maxPressure;
}
else {
outWintabInfo[i].type = GHOST_kEventButtonUp;
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 {
outWintabInfo[i].type = GHOST_kEventCursorMove;
}
}
}
m_wintab.sysButtonsPressed = pkt.pkButtons;
// Wintab does not support performance counters, so use low frequency counter instead
outWintabInfo[i].time = system->tickCountToMillis(pkt.pkTime);
outWintabInfo[i].tabletData = tabletData;
void GHOST_WindowWin32::bringTabletContextToFront()
{
if (!useTabletAPI(GHOST_kTabletWintab)) {
return;
}
return GHOST_kSuccess;
if (m_wintab.overlap && m_wintab.tablet) {
m_wintab.overlap(m_wintab.tablet, TRUE);
}
}
GHOST_TUns16 GHOST_WindowWin32::getDPIHint()

View File

@ -36,9 +36,8 @@
#endif
#include <wintab.h>
#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;
@ -46,13 +45,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 BOOL(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);
@ -233,14 +228,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;
};
@ -434,16 +422,12 @@ class GHOST_WindowWin32 : public GHOST_Window {
HCURSOR getStandardCursor(GHOST_TStandardCursor shape) const;
void loadCursor(bool visible, GHOST_TStandardCursor cursorShape) const;
/**
* Handle setup and switch between Wintab and Pointer APIs
* \param active Whether the window is or will be in an active state
*/
void updateWintab(bool active);
const GHOST_TabletData &GetTabletData()
{
return m_tabletData;
}
/**
* Query whether given tablet API should be used.
* \param api Tablet API to test.
*/
void setTabletData(GHOST_TabletData *tabletData);
bool useTabletAPI(GHOST_TTabletAPI api) const;
/**
@ -456,28 +440,10 @@ class GHOST_WindowWin32 : public GHOST_Window {
WPARAM wParam,
LPARAM lParam);
/**
* Handle Wintab coordinate changes when DisplayChange events occur.
*/
void processWintabDisplayChangeEvent();
/**
* Set tablet details when a cursor enters range
*/
void processWintabProximityEvent(bool inRange);
/**
* 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);
void processWin32TabletActivateEvent(WORD state);
void processWin32TabletInitEvent();
void processWin32TabletEvent(WPARAM wParam, LPARAM lParam);
void bringTabletContextToFront();
GHOST_TSuccess beginFullScreen() const
{
@ -497,19 +463,6 @@ class GHOST_WindowWin32 : public GHOST_Window {
*/
bool getMousePressed() const;
/**
* Get if there are currently pressed Wintab buttons associated to a Windows mouse button press
* \return True if there are currently any pressed Wintab buttons associated to a Windows
* mouse button press
*/
bool wintabSysButPressed() const;
/**
* Register a Wintab button has been associated to a Windows mouse button press
* \param event Whether the button was pressed or released
*/
void updateWintabSysBut(GHOST_MouseCaptureEventWin32 event);
/** Whether a tablet stylus is being tracked */
bool m_tabletInRange;
@ -593,49 +546,28 @@ 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;
/** Number of cursors currently in contact mapped to system buttons */
GHOST_TUns8 numSysButtons = 0;
/** Cursors currently in contact mapped to system buttons */
DWORD sysButtonsPressed = 0;
LONG maxPressure = 0;
LONG maxAzimuth = 0, maxAltitude = 0;
/* Queue size doesn't change once set, so reuse the same buffer */
std::vector<PACKET> pkts;
HCTX tablet;
LONG maxPressure;
LONG maxAzimuth, maxAltitude;
} m_wintab;
/**
* Wintab setup
*/
void initializeWintab();
/**
* Convert Wintab system mapped (mouse) buttons into Ghost button mask
*/
GHOST_TSuccess wintabMouseToGhost(UINT cursor,
DWORD physicalButton,
GHOST_TButtonMask &buttonMask);
GHOST_TWindowState m_normal_state;
/** user32 dll handle*/