From abbdfc8bf71f779a4c34a22a1df7bc37ae14fd4c Mon Sep 17 00:00:00 2001 From: Nicholas Rishel Date: Mon, 20 Apr 2020 18:46:57 -0700 Subject: Revert "Revert "Windows: support high resolution tablet pen events for Wintab"" This reverts commit e90d8422d0ed38743390f2184fde27550d7b48a7. --- intern/ghost/intern/GHOST_System.h | 2 +- intern/ghost/intern/GHOST_SystemWin32.cpp | 148 +++++++++- intern/ghost/intern/GHOST_SystemWin32.h | 10 + intern/ghost/intern/GHOST_WindowWin32.cpp | 451 +++++++++++++++++++----------- intern/ghost/intern/GHOST_WindowWin32.h | 118 ++++++-- 5 files changed, 528 insertions(+), 201 deletions(-) (limited to 'intern') diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h index d5b23d76016..a0d8b720ce1 100644 --- a/intern/ghost/intern/GHOST_System.h +++ b/intern/ghost/intern/GHOST_System.h @@ -237,7 +237,7 @@ class GHOST_System : public GHOST_ISystem { * Set which tablet API to use. Only affects Windows, other platforms have a single API. * \param api Enum indicating which API to use. */ - void setTabletAPI(GHOST_TTabletAPI api); + virtual void setTabletAPI(GHOST_TTabletAPI api); GHOST_TTabletAPI getTabletAPI(void); #ifdef WITH_INPUT_NDOF diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp index 08df56d295b..558143a2ead 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cpp +++ b/intern/ghost/intern/GHOST_SystemWin32.cpp @@ -941,15 +941,100 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type, window->updateMouseCapture(MouseReleased); } - if (window->m_tabletInRange) { - if (window->useTabletAPI(GHOST_kTabletNative)) { + /* 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)) { // 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, window->getTabletData()); + 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 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; } void GHOST_SystemWin32::processPointerEvents( @@ -1037,13 +1122,19 @@ 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)) { + if (window->m_tabletInRange || window->wintabSysButPressed()) { + if (window->useTabletAPI(GHOST_kTabletWintab) && + processWintabEvents(GHOST_kEventCursorMove, window)) { + return NULL; + } + else 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); @@ -1076,7 +1167,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind window, x_screen + x_accum, y_screen + y_accum, - window->getTabletData()); + GHOST_TABLET_DATA_NONE); } } else { @@ -1085,7 +1176,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind window, x_screen, y_screen, - window->getTabletData()); + GHOST_TABLET_DATA_NONE); } return NULL; } @@ -1202,7 +1293,6 @@ GHOST_Event *GHOST_SystemWin32::processWindowEvent(GHOST_TEventType type, if (type == GHOST_kEventWindowActivate) { system->getWindowManager()->setActiveWindow(window); - window->bringTabletContextToFront(); } return new GHOST_Event(system->getMilliSeconds(), type, window); @@ -1230,6 +1320,19 @@ 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; @@ -1467,15 +1570,17 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, } break; //////////////////////////////////////////////////////////////////////// - // Tablet events, processed + // Wintab events, processed //////////////////////////////////////////////////////////////////////// - case WT_PACKET: - window->processWin32TabletEvent(wParam, lParam); + case WT_INFOCHANGE: { + window->processWintabInfoChangeEvent(lParam); break; - case WT_CSRCHANGE: - case WT_PROXIMITY: - window->processWin32TabletInitEvent(); + } + case WT_PROXIMITY: { + bool inRange = LOWORD(lParam); + window->processWintabProximityEvent(inRange); break; + } //////////////////////////////////////////////////////////////////////// // Pointer events, processed //////////////////////////////////////////////////////////////////////// @@ -1614,7 +1719,9 @@ 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)); + + window->updateWintab(LOWORD(wParam) != WA_INACTIVE); + lResult = ::DefWindowProc(hwnd, msg, wParam, lParam); break; } @@ -1672,6 +1779,11 @@ 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(); @@ -1722,6 +1834,12 @@ 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 //////////////////////////////////////////////////////////////////////// diff --git a/intern/ghost/intern/GHOST_SystemWin32.h b/intern/ghost/intern/GHOST_SystemWin32.h index b91a9d7a154..06582d74918 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.h +++ b/intern/ghost/intern/GHOST_SystemWin32.h @@ -265,6 +265,16 @@ class GHOST_SystemWin32 : public GHOST_System { int mouseY, void *data); + /*************************************************************************************** + ** Modify tablet API + ***************************************************************************************/ + + /** + * Set which tablet API to use. + * \param api Enum indicating which API to use. + */ + void setTabletAPI(GHOST_TTabletAPI api) override; + protected: /** * Initializes the system. diff --git a/intern/ghost/intern/GHOST_WindowWin32.cpp b/intern/ghost/intern/GHOST_WindowWin32.cpp index 18a48c9169e..954f186bddd 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.cpp +++ b/intern/ghost/intern/GHOST_WindowWin32.cpp @@ -81,6 +81,7 @@ 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), @@ -89,10 +90,6 @@ 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; @@ -297,66 +294,25 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system, m_user32, "GetPointerTouchInfoHistory"); } - // 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); - } - } + 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); } + CoCreateInstance( CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (LPVOID *)&m_Bar); } @@ -370,8 +326,8 @@ GHOST_WindowWin32::~GHOST_WindowWin32() } if (m_wintab.handle) { - if (m_wintab.close && m_wintab.tablet) { - m_wintab.close(m_wintab.tablet); + if (m_wintab.close && m_wintab.context) { + m_wintab.close(m_wintab.context); } FreeLibrary(m_wintab.handle); @@ -819,6 +775,27 @@ 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 @@ -1022,6 +999,103 @@ 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 &outPointerInfo, WPARAM wParam, LPARAM lParam) { @@ -1104,28 +1178,20 @@ GHOST_TSuccess GHOST_WindowWin32::getPointerInfo( return GHOST_kSuccess; } -void GHOST_WindowWin32::setTabletData(GHOST_TabletData *pTabletData) +void GHOST_WindowWin32::processWintabDisplayChangeEvent() { - if (pTabletData) { - m_tabletData = *pTabletData; - } - else { - m_tabletData = GHOST_TABLET_DATA_NONE; - } -} + LOGCONTEXT lc_sys = {0}, lc_curr = {0}; -void GHOST_WindowWin32::processWin32TabletActivateEvent(WORD state) -{ - if (!useTabletAPI(GHOST_kTabletWintab)) { - return; - } + if (m_wintab.info && m_wintab.get && m_wintab.set && m_wintab.info(WTI_DEFSYSCTX, 0, &lc_sys)) { - if (m_wintab.enable && m_wintab.tablet) { - m_wintab.enable(m_wintab.tablet, state); + m_wintab.get(m_wintab.context, &lc_curr); - if (m_wintab.overlap && state) { - m_wintab.overlap(m_wintab.tablet, TRUE); - } + 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); } } @@ -1135,7 +1201,7 @@ bool GHOST_WindowWin32::useTabletAPI(GHOST_TTabletAPI api) const return true; } else if (m_system->getTabletAPI() == GHOST_kTabletAutomatic) { - if (m_wintab.tablet) + if (m_wintab.numDevices) return api == GHOST_kTabletWintab; else return api == GHOST_kTabletNative; @@ -1145,115 +1211,180 @@ bool GHOST_WindowWin32::useTabletAPI(GHOST_TTabletAPI api) const } } -void GHOST_WindowWin32::processWin32TabletInitEvent() +void GHOST_WindowWin32::processWintabProximityEvent(bool inRange) { if (!useTabletAPI(GHOST_kTabletWintab)) { return; } // Let's see if we can initialize tablet here - if (m_wintab.info && m_wintab.tablet) { + if (m_wintab.info && m_wintab.context) { AXIS Pressure, Orientation[3]; /* The maximum tablet size */ BOOL pressureSupport = m_wintab.info(WTI_DEVICES, DVC_NPRESSURE, &Pressure); - if (pressureSupport) - m_wintab.maxPressure = Pressure.axMax; - else - m_wintab.maxPressure = 0; + m_wintab.maxPressure = pressureSupport ? Pressure.axMax : 0; BOOL tiltSupport = m_wintab.info(WTI_DEVICES, DVC_ORIENTATION, &Orientation); - if (tiltSupport) { - /* does the tablet support azimuth ([0]) and altitude ([1]) */ - if (Orientation[0].axResolution && Orientation[1].axResolution) { - m_wintab.maxAzimuth = Orientation[0].axMax; - m_wintab.maxAltitude = Orientation[1].axMax; - } - else { /* no so dont do tilt stuff */ - m_wintab.maxAzimuth = m_wintab.maxAltitude = 0; - } + /* 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; } } - m_tabletData.Active = GHOST_kTabletModeNone; + m_tabletInRange = inRange; +} + +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; + } } -void GHOST_WindowWin32::processWin32TabletEvent(WPARAM wParam, LPARAM lParam) +GHOST_TSuccess GHOST_WindowWin32::getWintabInfo(std::vector &outWintabInfo) { if (!useTabletAPI(GHOST_kTabletWintab)) { - return; + 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; - } + if (!(m_wintab.packetsGet && m_wintab.context)) { + return GHOST_kFailure; + } - if (m_wintab.maxPressure > 0) { - m_tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_wintab.maxPressure; - } - else { - m_tabletData.Pressure = 1.0f; - } + GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem(); - if ((m_wintab.maxAzimuth > 0) && (m_wintab.maxAltitude > 0)) { - ORIENTATION ort = pkt.pkOrientation; - float vecLen; - float altRad, azmRad; /* in radians */ + const int numPackets = m_wintab.packetsGet( + m_wintab.context, m_wintab.pkts.size(), m_wintab.pkts.data()); + outWintabInfo.resize(numPackets); - /* - * 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. - */ + 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; + } - /* 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); + if (m_wintab.maxPressure > 0) { + tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_wintab.maxPressure; + } - /* find length of the stylus' projected vector on the XY plane */ - vecLen = cos(altRad); + 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); + } - /* 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); + 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; } else { - m_tabletData.Xtilt = 0.0f; - m_tabletData.Ytilt = 0.0f; + outWintabInfo[i].type = GHOST_kEventButtonUp; } } - } -} + else { + outWintabInfo[i].type = GHOST_kEventCursorMove; + } -void GHOST_WindowWin32::bringTabletContextToFront() -{ - if (!useTabletAPI(GHOST_kTabletWintab)) { - return; - } + m_wintab.sysButtonsPressed = pkt.pkButtons; - if (m_wintab.overlap && m_wintab.tablet) { - m_wintab.overlap(m_wintab.tablet, TRUE); + // Wintab does not support performance counters, so use low frequency counter instead + outWintabInfo[i].time = system->tickCountToMillis(pkt.pkTime); + outWintabInfo[i].tabletData = tabletData; } + + return GHOST_kSuccess; } GHOST_TUns16 GHOST_WindowWin32::getDPIHint() diff --git a/intern/ghost/intern/GHOST_WindowWin32.h b/intern/ghost/intern/GHOST_WindowWin32.h index 224ff53bf7b..a3aa511bc37 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.h +++ b/intern/ghost/intern/GHOST_WindowWin32.h @@ -37,8 +37,9 @@ #include #include -#define PACKETDATA (PK_BUTTONS | PK_NORMAL_PRESSURE | PK_ORIENTATION | PK_CURSOR) -#define PACKETMODE PK_BUTTONS +#define PACKETDATA \ + (PK_BUTTONS | PK_NORMAL_PRESSURE | PK_ORIENTATION | PK_CURSOR | PK_X | PK_Y | PK_TIME) +#define PACKETMODE 0 #include class GHOST_SystemWin32; @@ -46,9 +47,13 @@ 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_WTPacket)(HCTX, UINT, LPVOID); +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_WTEnable)(HCTX, BOOL); typedef BOOL(API *GHOST_WIN32_WTOverlap)(HCTX, BOOL); @@ -229,7 +234,14 @@ 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; }; @@ -423,12 +435,16 @@ 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; - } + /** + * 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); - void setTabletData(GHOST_TabletData *tabletData); + /** + * Query whether given tablet API should be used. + * \param api Tablet API to test. + */ bool useTabletAPI(GHOST_TTabletAPI api) const; /** @@ -441,10 +457,28 @@ class GHOST_WindowWin32 : public GHOST_Window { WPARAM wParam, LPARAM lParam); - void processWin32TabletActivateEvent(WORD state); - void processWin32TabletInitEvent(); - void processWin32TabletEvent(WPARAM wParam, LPARAM lParam); - void bringTabletContextToFront(); + /** + * 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 &outWintabInfo); GHOST_TSuccess beginFullScreen() const { @@ -464,6 +498,19 @@ 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; @@ -547,28 +594,49 @@ 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; + HMODULE handle = NULL; /** API functions */ - GHOST_WIN32_WTInfo info; - GHOST_WIN32_WTOpen open; - GHOST_WIN32_WTClose close; - GHOST_WIN32_WTPacket packet; - GHOST_WIN32_WTEnable enable; - GHOST_WIN32_WTOverlap overlap; + 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; /** Stores the Tablet context if detected Tablet features using WinTab.dll */ - HCTX tablet; - LONG maxPressure; - LONG maxAzimuth, maxAltitude; + 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 pkts; } 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*/ -- cgit v1.2.3