diff options
author | Nicholas Rishel <nicholas_rishel> | 2020-12-25 03:25:07 +0300 |
---|---|---|
committer | Nicholas Rishel <rishel.nick@gmail.com> | 2020-12-25 03:41:19 +0300 |
commit | 565ea3df6077a1af5995b9b4defec9b03f3c6c29 (patch) | |
tree | 8a4d5af1f3aca7442e3061aefead39f9c8b631b5 /intern | |
parent | af316d276144b905cf6cf5a1b1d7ae727d545e2f (diff) |
Simplification of Wintab event handling.
Previously Wintab packets were added to a local queue to be processed
during Win32 mouse events, in order to correlate Wintab to Win32
mouse buttons. Wintab packets before Win32 mouse down events were
expired on a timer.
This commit drives mouse events during Wintab events when a device is
in range. When a Wintab button is found it is dispatched if an
equivalent event can be popped from the Win32 event queue. If a Win32
mouse button event is not associated with a Wintab event, it falls
through to WM_BUTTON handling. All Wintab packets are handled as they
are received.
Reviewed By: brecht
Differential Revision: https://developer.blender.org/D9908
Diffstat (limited to 'intern')
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWin32.cpp | 292 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWin32.h | 16 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowWin32.cpp | 345 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowWin32.h | 99 |
4 files changed, 298 insertions, 454 deletions
diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp index 7e800619dda..d1822fe46fd 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cpp +++ b/intern/ghost/intern/GHOST_SystemWin32.cpp @@ -569,13 +569,13 @@ GHOST_TSuccess GHOST_SystemWin32::getButtons(GHOST_Buttons &buttons) const */ bool swapped = ::GetSystemMetrics(SM_SWAPBUTTON) == TRUE; - bool down = HIBYTE(::GetKeyState(VK_LBUTTON)) != 0; + bool down = HIBYTE(::GetAsyncKeyState(VK_LBUTTON)) != 0; buttons.set(swapped ? GHOST_kButtonMaskRight : GHOST_kButtonMaskLeft, down); - down = HIBYTE(::GetKeyState(VK_MBUTTON)) != 0; + down = HIBYTE(::GetAsyncKeyState(VK_MBUTTON)) != 0; buttons.set(GHOST_kButtonMaskMiddle, down); - down = HIBYTE(::GetKeyState(VK_RBUTTON)) != 0; + down = HIBYTE(::GetAsyncKeyState(VK_RBUTTON)) != 0; buttons.set(swapped ? GHOST_kButtonMaskLeft : GHOST_kButtonMaskRight, down); return GHOST_kSuccess; } @@ -939,148 +939,106 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type, { GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem(); - if (type == GHOST_kEventButtonDown) { - window->updateMouseCapture(MousePressed); - } - else if (type == GHOST_kEventButtonUp) { - window->updateMouseCapture(MouseReleased); - } + GHOST_TabletData td = window->m_tabletInRange ? window->getLastTabletData() : + GHOST_TABLET_DATA_NONE; - /* 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->useTabletAPI(GHOST_kTabletWintab) && - (window->m_tabletInRange || window->wintabSysButPressed()) && - processWintabEvent(type, window, mask, window->getMousePressed())) { - /* Wintab processing only handles in-contact events. */ - return NULL; - } + /* Ensure button click occurs at its intended position. */ + DWORD msgPos = ::GetMessagePos(); + GHOST_TInt32 x_screen = GET_X_LPARAM(msgPos), y_screen = GET_Y_LPARAM(msgPos); + system->pushEvent(new GHOST_EventCursor( + system->getMilliSeconds(), GHOST_kEventCursorMove, window, x_screen, y_screen, td)); - return new GHOST_EventButton( - system->getMilliSeconds(), type, window, mask, GHOST_TABLET_DATA_NONE); + window->updateMouseCapture(type == GHOST_kEventButtonDown ? MousePressed : MouseReleased); + return new GHOST_EventButton(system->getMilliSeconds(), type, window, mask, td); } -GHOST_TSuccess GHOST_SystemWin32::processWintabEvent(GHOST_TEventType type, - GHOST_WindowWin32 *window, - GHOST_TButtonMask mask, - bool mousePressed) +void GHOST_SystemWin32::processWintabEvent(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 (!mousePressed && !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()) { - /* Assume there may be no button down event currently in the queue. */ - wtiIter = wintabInfo.end(); - - for (auto it = wintabInfo.begin(); it != wintabInfo.end(); it++) { - if (it->type == GHOST_kEventButtonDown) { - wtiIter = it; - } - } + return; } - bool unhandledButton = type != GHOST_kEventCursorMove; - - for (; wtiIter != wintabInfo.end(); wtiIter++) { - auto info = *wtiIter; - + 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: { - /* 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. */ system->pushEvent(new GHOST_EventCursor( info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData)); - if (type == GHOST_kEventButtonDown && mask == info.button) { + + 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)); - unhandledButton = false; } - 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)); - if (type == GHOST_kEventButtonUp && mask == info.button) { - unhandledButton = false; + 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)); } - window->updateWintabSysBut(MouseReleased); break; + } default: break; } } - - /* No Wintab button found correlating to the system button event, handle it too. - * - * Wintab button up events may be handled during WM_MOUSEMOVE, before their corresponding - * WM_*BUTTONUP event has fired, which results in two GHOST Button up events for a single Wintab - * associated button event. Alternatively this Windows button up event may have been generated - * from a non-stylus device such as a button on the tablet pad and needs to be handled for some - * workflows. - * - * The ambiguity introduced by Windows and Wintab buttons being asynchronous and having no - * definitive way to associate each, and that the Wintab API does not provide enough information - * to differentiate whether the stylus down is or is not modified by another button to a - * non-mouse mapping, means that we must pessimistically generate mouse up events when we are - * unsure of an association to prevent the mouse locking into a down state. */ - if (unhandledButton) { - if (!window->wintabSysButPressed()) { - GHOST_TInt32 x, y; - system->getCursorPosition(x, y); - system->pushEvent(new GHOST_EventCursor(system->getMilliSeconds(), - GHOST_kEventCursorMove, - window, - x, - y, - GHOST_TABLET_DATA_NONE)); - } - system->pushEvent(new GHOST_EventButton( - system->getMilliSeconds(), type, window, mask, GHOST_TABLET_DATA_NONE)); - } - - return GHOST_kSuccess; } void GHOST_SystemWin32::processPointerEvent( UINT type, GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam, bool &eventHandled) { - std::vector<GHOST_PointerInfoWin32> pointerInfo; - GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem(); - /* Pointer events might fire when changing windows for a device which is set to use Wintab, even * when when Wintab is left enabled but set to the bottom of Wintab overlap order. */ if (!window->useTabletAPI(GHOST_kTabletNative)) { return; } + GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem(); + std::vector<GHOST_PointerInfoWin32> pointerInfo; + if (window->getPointerInfo(pointerInfo, wParam, lParam) != GHOST_kSuccess) { return; } @@ -1148,36 +1106,16 @@ void GHOST_SystemWin32::processPointerEvent( GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window) { - GHOST_TInt32 x_screen, y_screen; - GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem(); - - GHOST_TabletData tabletData = GHOST_TABLET_DATA_NONE; - - if (window->m_tabletInRange || window->wintabSysButPressed()) { - if (window->useTabletAPI(GHOST_kTabletWintab) && - processWintabEvent( - GHOST_kEventCursorMove, window, GHOST_kButtonMaskNone, window->getMousePressed())) { - 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. - * - * Populate tablet data so that cursor is recognized as an absolute position device. */ - tabletData.Active = GHOST_kTabletModeStylus; - tabletData.Pressure = 0.0f; - tabletData.Xtilt = 0.0f; - tabletData.Ytilt = 0.0f; + if (window->m_tabletInRange) { + return NULL; } + GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem(); + GHOST_TInt32 x_screen, y_screen; + system->getCursorPosition(x_screen, y_screen); - if (window->getCursorGrabModeIsWarp() && !window->m_tabletInRange) { + if (window->getCursorGrabModeIsWarp()) { GHOST_TInt32 x_new = x_screen; GHOST_TInt32 y_new = y_screen; GHOST_TInt32 x_accum, y_accum; @@ -1205,12 +1143,16 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind window, x_screen + x_accum, y_screen + y_accum, - tabletData); + GHOST_TABLET_DATA_NONE); } } else { - return new GHOST_EventCursor( - system->getMilliSeconds(), GHOST_kEventCursorMove, window, x_screen, y_screen, tabletData); + return new GHOST_EventCursor(system->getMilliSeconds(), + GHOST_kEventCursorMove, + window, + x_screen, + y_screen, + GHOST_TABLET_DATA_NONE); } return NULL; } @@ -1320,6 +1262,23 @@ 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) { @@ -1359,12 +1318,10 @@ 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, - !::IsIconic(windowsWindow->getHWND())); + GHOST_WindowWin32 *windowWin32 = (GHOST_WindowWin32 *)win; + windowWin32->setWintabEnabled(windowWin32->useTabletAPI(GHOST_kTabletWintab)); } } @@ -1609,15 +1566,23 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, //////////////////////////////////////////////////////////////////////// case WT_INFOCHANGE: { window->processWintabInfoChangeEvent(lParam); + eventHandled = true; break; } + case WT_CSRCHANGE: + window->updateWintabCursorInfo(); + eventHandled = true; + break; case WT_PROXIMITY: { - bool inRange = LOWORD(lParam); - window->processWintabProximityEvent(inRange); + if (window->useTabletAPI(GHOST_kTabletWintab)) { + window->m_tabletInRange = LOWORD(lParam); + } + eventHandled = true; break; } case WT_PACKET: - window->updateWintabEventsSyncTime(); + processWintabEvent(window); + eventHandled = true; break; //////////////////////////////////////////////////////////////////////// // Pointer events, processed @@ -1667,6 +1632,15 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, } 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); + } + event = processCursorEvent(window); break; case WM_MOUSEWHEEL: { @@ -1709,7 +1683,10 @@ 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); + break; //////////////////////////////////////////////////////////////////////// // Mouse events, ignored //////////////////////////////////////////////////////////////////////// @@ -1725,7 +1702,6 @@ 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 //////////////////////////////////////////////////////////////////////// @@ -1758,8 +1734,6 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, if (LOWORD(wParam) == WA_INACTIVE) window->lostMouseCapture(); - window->updateWintab(LOWORD(wParam) != WA_INACTIVE, !::IsIconic(window->getHWND())); - lResult = ::DefWindowProc(hwnd, msg, wParam, lParam); break; } @@ -1801,6 +1775,8 @@ 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 @@ -1808,21 +1784,13 @@ 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. */ - /* 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 { - event = processWindowEvent(GHOST_kEventWindowSize, window); - } + event = processWindowSizeEvent(window); - /* Window might be minimized while inactive. When a window is inactive but not minimized, - * Wintab is left enabled (to catch the case where a pen is used to activate a window). - * When an inactive window is minimized, we need to disable Wintab. */ - if (msg == WM_SIZE && wParam == SIZE_MINIMIZED) { - window->updateWintab(false, false); + if (wParam == SIZE_MINIMIZED) { + window->setWintabEnabled(false); + } + else if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) { + window->setWintabEnabled(true); } break; diff --git a/intern/ghost/intern/GHOST_SystemWin32.h b/intern/ghost/intern/GHOST_SystemWin32.h index 00b626511ab..ca227278fa7 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.h +++ b/intern/ghost/intern/GHOST_SystemWin32.h @@ -322,16 +322,9 @@ class GHOST_SystemWin32 : public GHOST_System { /** * Creates tablet events from Wintab events. - * \param type: The type of pointer event. * \param window: The window receiving the event (the active window). - * \param mask: The button mask of the calling event. - * \param mousePressed: Whether the mouse is currently pressed. - * \return True if the method handled the event. */ - static GHOST_TSuccess processWintabEvent(GHOST_TEventType type, - GHOST_WindowWin32 *window, - GHOST_TButtonMask mask, - bool mousePressed); + static void processWintabEvent(GHOST_WindowWin32 *window); /** * Creates tablet events from pointer events. @@ -377,6 +370,13 @@ 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. * \param window: The window receiving the event (the active window). diff --git a/intern/ghost/intern/GHOST_WindowWin32.cpp b/intern/ghost/intern/GHOST_WindowWin32.cpp index a4cbf66b22d..f5088c6505e 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.cpp +++ b/intern/ghost/intern/GHOST_WindowWin32.cpp @@ -72,6 +72,7 @@ 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), @@ -309,8 +310,7 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system, (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(m_system->m_windowFocus, m_system->m_windowFocus); + setWintabEnabled(true); } CoCreateInstance( @@ -326,13 +326,12 @@ GHOST_WindowWin32::~GHOST_WindowWin32() } if (m_wintab.handle) { - updateWintab(false, false); + setWintabEnabled(false); if (m_wintab.close && m_wintab.context) { m_wintab.close(m_wintab.context); } FreeLibrary(m_wintab.handle); - memset(&m_wintab, 0, sizeof(m_wintab)); } if (m_user32) { @@ -771,32 +770,6 @@ void GHOST_WindowWin32::updateMouseCapture(GHOST_MouseCaptureEventWin32 event) } } -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,28 +973,6 @@ GHOST_TSuccess GHOST_WindowWin32::hasCursorShape(GHOST_TStandardCursor cursorSha return (getStandardCursor(cursorShape)) ? GHOST_kSuccess : GHOST_kFailure; } -void GHOST_WindowWin32::updateWintab(bool active, bool visible) -{ - if (m_wintab.enable && m_wintab.overlap && m_wintab.context) { - bool enable = useTabletAPI(GHOST_kTabletWintab) && visible; - bool overlap = enable && active; - - /* 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, enable); - m_wintab.overlap(m_wintab.context, overlap); - - if (!overlap) { - /* 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. */ @@ -1034,8 +985,6 @@ void GHOST_WindowWin32::initializeWintab() if (m_wintab.open && m_wintab.info && m_wintab.queueSizeGet && m_wintab.queueSizeSet && m_wintab.info(WTI_DEFSYSCTX, 0, &lc)) { - /* The pressure and orientation (tilt) */ - AXIS Pressure, Orientation[3]; lc.lcPktData = PACKETDATA; lc.lcPktMode = PACKETMODE; lc.lcMoveMask = PACKETDATA; @@ -1046,19 +995,7 @@ void GHOST_WindowWin32::initializeWintab() m_wintab.info(WTI_INTERFACE, IFC_NDEVICES, &m_wintab.numDevices); - BOOL pressureSupport = m_wintab.info(WTI_DEVICES, DVC_NPRESSURE, &Pressure); - m_wintab.maxPressure = pressureSupport ? Pressure.axMax : 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) { - /* 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; - } + 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); @@ -1171,9 +1108,63 @@ GHOST_TSuccess GHOST_WindowWin32::getPointerInfo( } } + if (!outPointerInfo.empty()) { + lastTabletData = outPointerInfo.back().tabletData; + } + return GHOST_kSuccess; } +void GHOST_WindowWin32::setWintabEnabled(bool enable) +{ + 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); + } + } + } +} + +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.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)) { + /* Set tablet as not in range, proximity event may not occur. */ + m_tabletInRange = false; + /* 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}; @@ -1207,48 +1198,38 @@ bool GHOST_WindowWin32::useTabletAPI(GHOST_TTabletAPI api) const } } -void GHOST_WindowWin32::processWintabProximityEvent(bool inRange) +void GHOST_WindowWin32::updateWintabCursorInfo() { - if (!useTabletAPI(GHOST_kTabletWintab)) { - return; - } - - // Let's see if we can initialize tablet here if (m_wintab.info && m_wintab.context) { - AXIS Pressure, Orientation[3]; /* The maximum tablet size */ + AXIS Pressure, Orientation[3]; BOOL pressureSupport = m_wintab.info(WTI_DEVICES, DVC_NPRESSURE, &Pressure); m_wintab.maxPressure = pressureSupport ? Pressure.axMax : 0; BOOL tiltSupport = m_wintab.info(WTI_DEVICES, DVC_ORIENTATION, &Orientation); - /* does the tablet support azimuth ([0]) and altitude ([1]) */ + /* 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 */ + else { m_wintab.maxAzimuth = m_wintab.maxAltitude = 0; } } - - m_tabletInRange = inRange; } void GHOST_WindowWin32::processWintabInfoChangeEvent(LPARAM lParam) { - GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem(); - - // Update number of connected Wintab digitizers + /* 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, - !::IsIconic(m_hWnd)); + if (useTabletAPI(GHOST_kTabletWintab)) { + setWintabEnabled(true); + } } } -GHOST_TSuccess GHOST_WindowWin32::wintabMouseToGhost(UINT cursor, - WORD physicalButton, - GHOST_TButtonMask &ghostButton) +GHOST_TButtonMask GHOST_WindowWin32::wintabMouseToGhost(UINT cursor, WORD physicalButton) { const WORD numButtons = 32; BYTE logicalButtons[numButtons] = {0}; @@ -1258,198 +1239,120 @@ GHOST_TSuccess GHOST_WindowWin32::wintabMouseToGhost(UINT cursor, m_wintab.info(WTI_CURSORS + cursor, CSR_SYSBTNMAP, &systemButtons); if (physicalButton >= numButtons) { - return GHOST_kFailure; + return GHOST_kButtonMaskNone; } BYTE lb = logicalButtons[physicalButton]; if (lb >= numButtons) { - return GHOST_kFailure; + return GHOST_kButtonMaskNone; } switch (systemButtons[lb]) { case SBN_LCLICK: - ghostButton = GHOST_kButtonMaskLeft; - return GHOST_kSuccess; + return GHOST_kButtonMaskLeft; case SBN_RCLICK: - ghostButton = GHOST_kButtonMaskRight; - return GHOST_kSuccess; + return GHOST_kButtonMaskRight; case SBN_MCLICK: - ghostButton = GHOST_kButtonMaskMiddle; - return GHOST_kSuccess; + return GHOST_kButtonMaskMiddle; default: - return GHOST_kFailure; + return GHOST_kButtonMaskNone; } } GHOST_TSuccess GHOST_WindowWin32::getWintabInfo(std::vector<GHOST_WintabInfoWin32> &outWintabInfo) { - if (!useTabletAPI(GHOST_kTabletWintab)) { - return GHOST_kFailure; - } - - if (!(m_wintab.packetsGet && m_wintab.context)) { + if (!(useTabletAPI(GHOST_kTabletWintab) && m_wintab.packetsGet && m_wintab.context)) { return GHOST_kFailure; } GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem(); - updateWintabEvents(); - - auto &pendingEvents = m_wintab.pendingEvents; - size_t pendingEventSize = pendingEvents.size(); - outWintabInfo.resize(pendingEventSize); + 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 < pendingEventSize; i++) { - PACKET pkt = pendingEvents.front(); - pendingEvents.pop(); + for (int i = 0; i < numPackets; i++) { + PACKET pkt = m_wintab.pkts[i]; + GHOST_WintabInfoWin32 &out = outWintabInfo[i]; - GHOST_TabletData tabletData = GHOST_TABLET_DATA_NONE; - switch (pkt.pkCursor % 3) { /* % 3 for multiple devices ("DualTrack") */ + out.tabletData = GHOST_TABLET_DATA_NONE; + /* % 3 for multiple devices ("DualTrack"). */ + switch (pkt.pkCursor % 3) { case 0: - tabletData.Active = GHOST_kTabletModeNone; /* puck - not yet supported */ + /* Puck - processed as mouse. */ + out.tabletData.Active = GHOST_kTabletModeNone; break; case 1: - tabletData.Active = GHOST_kTabletModeStylus; /* stylus */ + out.tabletData.Active = GHOST_kTabletModeStylus; break; case 2: - tabletData.Active = GHOST_kTabletModeEraser; /* eraser */ + out.tabletData.Active = GHOST_kTabletModeEraser; break; } + out.x = pkt.pkX; + out.y = pkt.pkY; + if (m_wintab.maxPressure > 0) { - tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_wintab.maxPressure; + 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 */ + 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. + * 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. * - * 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. + * 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 */ + /* 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 */ + /* 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. */ + out.tabletData.Xtilt = sin(azmRad) * vecLen; + out.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. Examples include Wacom's Bamboo modifying button events in the queue when - * peeked, or missing events when entering the window when the context is not on top. */ - DWORD buttonsChanged = m_wintab.sysButtonsPressed ^ pkt.pkButtons; - - /* 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 (buttonsChanged && - wintabMouseToGhost(pkt.pkCursor, physicalButton, outWintabInfo[i].button)) { - if (buttonsChanged & pkt.pkButtons) { - outWintabInfo[i].type = GHOST_kEventButtonDown; - } - else { - outWintabInfo[i].type = GHOST_kEventButtonUp; - } - } - else { - outWintabInfo[i].type = GHOST_kEventCursorMove; + out.button = wintabMouseToGhost(pkt.pkCursor, LOWORD(pkt.pkButtons)); + switch (HIWORD(pkt.pkButtons)) { + case TBN_NONE: + out.type = GHOST_kEventCursorMove; + break; + case TBN_DOWN: + out.type = GHOST_kEventButtonDown; + break; + case TBN_UP: + out.type = GHOST_kEventButtonUp; + break; } - m_wintab.sysButtonsPressed = pkt.pkButtons; - - outWintabInfo[i].time = system->millisSinceStart(pkt.pkTime); - outWintabInfo[i].tabletData = tabletData; + out.time = system->tickCountToMillis(pkt.pkTime); } - return GHOST_kSuccess; -} - -void GHOST_WindowWin32::updateWintabEvents() -{ - readWintabEvents(); - // When a Wintab device is used to leave window focus, some of it's packets are periodically not - // queued in time to be flushed. Reading packets needs to occur before expiring packets to clear - // these from the queue. - expireWintabEvents(); -} - -void GHOST_WindowWin32::updateWintabEventsSyncTime() -{ - readWintabEvents(); - - if (!m_wintab.pendingEvents.empty()) { - auto lastEvent = m_wintab.pendingEvents.back(); - m_wintab.sysTimeOffset = ::GetTickCount() - lastEvent.pkTime; - } - - expireWintabEvents(); -} - -void GHOST_WindowWin32::readWintabEvents() -{ - if (!(m_wintab.packetsGet && m_wintab.context)) { - return; + if (!outWintabInfo.empty()) { + lastTabletData = outWintabInfo.back().tabletData; } - auto &pendingEvents = m_wintab.pendingEvents; - - /* Get new packets. */ - const int numPackets = m_wintab.packetsGet( - m_wintab.context, m_wintab.pkts.size(), m_wintab.pkts.data()); - - for (int i = 0; i < numPackets; i++) { - pendingEvents.push(m_wintab.pkts[i]); - } + return GHOST_kSuccess; } -/* Wintab (per documentation but may vary with implementation) does not update when its event - * buffer is full. This is an issue because we need some synchronization point between Wintab - * events and Win32 events, so we can't drain and process the queue immediately. We need to - * associate Wintab mouse events to Win32 mouse events because Wintab buttons are modal (a button - * associated to left click is not always a left click) and there's no way to reconstruct their - * mode from the Wintab API alone. There is no guaranteed ordering between Wintab and Win32 mouse - * events and no documented time stamp shared between the two, so we synchronize on mouse button - * events. */ -void GHOST_WindowWin32::expireWintabEvents() +GHOST_TabletData GHOST_WindowWin32::getLastTabletData() { - auto &pendingEvents = m_wintab.pendingEvents; - - DWORD currTime = ::GetTickCount() - m_wintab.sysTimeOffset; - DWORD millisTimeout = 300; - while (!pendingEvents.empty()) { - DWORD pkTime = pendingEvents.front().pkTime; - - if (currTime > pkTime + millisTimeout) { - pendingEvents.pop(); - } - else { - break; - } - } + return lastTabletData; } GHOST_TUns16 GHOST_WindowWin32::getDPIHint() diff --git a/intern/ghost/intern/GHOST_WindowWin32.h b/intern/ghost/intern/GHOST_WindowWin32.h index 829bbdea051..1cff8b2036e 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.h +++ b/intern/ghost/intern/GHOST_WindowWin32.h @@ -41,7 +41,7 @@ // 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 PACKETMODE PK_BUTTONS #include <pktdef.h> class GHOST_SystemWin32; @@ -438,13 +438,6 @@ class GHOST_WindowWin32 : public GHOST_Window { 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. - * \param visible: Whether the window is currently (or will be) visible). - */ - void updateWintab(bool active, bool visible); - - /** * Query whether given tablet API should be used. * \param api: Tablet API to test. */ @@ -462,15 +455,26 @@ class GHOST_WindowWin32 : public GHOST_Window { 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); + + /** * Handle Wintab coordinate changes when DisplayChange events occur. */ void processWintabDisplayChangeEvent(); /** - * Set tablet details when a cursor enters range. - * \param inRange: Whether the Wintab device is in tracking range. + * Updates cached Wintab properties for current cursor. */ - void processWintabProximityEvent(bool inRange); + void updateWintabCursorInfo(); /** * Handle Wintab info changes such as change in number of connected tablets. @@ -486,14 +490,10 @@ class GHOST_WindowWin32 : public GHOST_Window { GHOST_TSuccess getWintabInfo(std::vector<GHOST_WintabInfoWin32> &outWintabInfo); /** - * Updates pending Wintab events and syncs Wintab time with OS time. - */ - void updateWintabEventsSyncTime(); - - /** - * Updates pending Wintab events. + * Get the most recent tablet data. + * \return Most recent tablet data. */ - void updateWintabEvents(); + GHOST_TabletData getLastTabletData(); GHOST_TSuccess beginFullScreen() const { @@ -507,24 +507,8 @@ class GHOST_WindowWin32 : public GHOST_Window { GHOST_TUns16 getDPIHint() override; - /** - * Get whether there are currently any mouse buttons pressed. - * \return True if there are any currently pressed mouse buttons. - */ - 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 the mouse is either over or captured by the window. */ + bool m_mousePresent; /** Whether a tablet stylus is being tracked. */ bool m_tabletInRange; @@ -582,28 +566,28 @@ class GHOST_WindowWin32 : public GHOST_Window { int hotY, bool canInvertColor); - /** Pointer to system */ + /** Pointer to system. */ GHOST_SystemWin32 *m_system; - /** Pointer to COM IDropTarget implementor */ + /** Pointer to COM IDropTarget implementor. */ GHOST_DropTargetWin32 *m_dropTarget; /** Window handle. */ HWND m_hWnd; /** Device context handle. */ HDC m_hDC; - /** Flag for if window has captured the mouse */ + /** Flag for if window has captured the mouse. */ bool m_hasMouseCaptured; - /** Flag if an operator grabs the mouse with WM_cursor_grab_enable/ungrab() - * Multiple grabs must be released with a single ungrab */ + /** Flag if an operator grabs the mouse with WM_cursor_grab_enable/ungrab(). + * Multiple grabs must be released with a single ungrab. */ bool m_hasGrabMouse; - /** Count of number of pressed buttons */ + /** Count of number of pressed buttons. */ int m_nPressedButtons; - /** HCURSOR structure of the custom cursor */ + /** HCURSOR structure of the custom cursor. */ HCURSOR m_customCursor; - /** request GL context aith alpha channel */ + /** Request GL context aith alpha channel. */ bool m_wantAlphaBackground; - /** ITaskbarList3 structure for progress bar*/ + /** ITaskbarList3 structure for progress bar. */ ITaskbarList3 *m_Bar; static const wchar_t *s_windowClassName; @@ -626,42 +610,31 @@ class GHOST_WindowWin32 : public GHOST_Window { GHOST_WIN32_WTEnable enable = NULL; GHOST_WIN32_WTOverlap overlap = NULL; - /** Stores the Tablet context if detected Tablet features using WinTab.dll */ + /** Stores the Tablet context if detected Tablet features using WinTab.dll. */ HCTX context = NULL; - /** Number of connected Wintab digitizers */ + /** 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; - DWORD sysTimeOffset = 0; LONG maxPressure = 0; LONG maxAzimuth = 0, maxAltitude = 0; /** Reusable buffer to read in Wintab Packets. */ std::vector<PACKET> pkts; - /** Queue of packets to process. */ - std::queue<PACKET> pendingEvents; } m_wintab; + /** Most recent tablet data. */ + GHOST_TabletData lastTabletData = GHOST_TABLET_DATA_NONE; + /** * Wintab setup. */ void initializeWintab(); - void readWintabEvents(); - - void expireWintabEvents(); - /** * 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. - * \param buttonMask: Return pointer for button found. - * \return Whether an associated button was found. + * \return The system mapped button. */ - GHOST_TSuccess wintabMouseToGhost(UINT cursor, - WORD physicalButton, - GHOST_TButtonMask &buttonMask); + GHOST_TButtonMask wintabMouseToGhost(UINT cursor, WORD physicalButton); GHOST_TWindowState m_normal_state; |