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
This commit is contained in:
Nicholas Rishel 2020-12-24 16:25:07 -08:00 committed by Nicholas Rishel
parent af316d2761
commit 565ea3df60
Notes: blender-bot 2023-02-14 03:52:45 +01:00
Referenced by issue #84144, Viewport navigation fails with continuous grab
Referenced by issue #83817, Tablet API not working
Referenced by issue #83715, Wacom Pen on Windows lost click precision in 2.92
Referenced by issue #82779, Wintab tablet issues
Referenced by issue #82430, Huion WH 1409 v2 viewport rotation with pen side buttons not working anymore in latest 2.92 alpha
4 changed files with 299 additions and 455 deletions

View File

@ -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;
return;
}
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;
}
}
}
bool unhandledButton = type != GHOST_kEventCursorMove;
for (; wtiIter != wintabInfo.end(); wtiIter++) {
auto info = *wtiIter;
for (auto info : wintabInfo) {
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. */
case GHOST_kEventCursorMove: {
system->pushEvent(new GHOST_EventCursor(
info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
if (type == GHOST_kEventButtonDown && mask == info.button) {
break;
}
case GHOST_kEventButtonDown: {
system->pushEvent(new GHOST_EventCursor(
info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
UINT message;
switch (info.button) {
case GHOST_kButtonMaskLeft:
message = WM_LBUTTONDOWN;
break;
case GHOST_kButtonMaskRight:
message = WM_RBUTTONDOWN;
break;
case GHOST_kButtonMaskMiddle:
message = WM_MBUTTONDOWN;
break;
default:
continue;
}
MSG msg;
if (PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD) &&
WM_QUIT != msg.message) {
window->updateMouseCapture(MousePressed);
system->pushEvent(
new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
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;

View File

@ -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.
@ -376,6 +369,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.

View File

@ -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();
const int numPackets = m_wintab.packetsGet(
m_wintab.context, m_wintab.pkts.size(), m_wintab.pkts.data());
outWintabInfo.resize(numPackets);
auto &pendingEvents = m_wintab.pendingEvents;
size_t pendingEventSize = pendingEvents.size();
outWintabInfo.resize(pendingEventSize);
for (int i = 0; i < numPackets; i++) {
PACKET pkt = m_wintab.pkts[i];
GHOST_WintabInfoWin32 &out = outWintabInfo[i];
for (int i = 0; i < pendingEventSize; i++) {
PACKET pkt = pendingEvents.front();
pendingEvents.pop();
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++;
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;
}
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.time = system->tickCountToMillis(pkt.pkTime);
}
m_wintab.sysButtonsPressed = pkt.pkButtons;
outWintabInfo[i].time = system->millisSinceStart(pkt.pkTime);
outWintabInfo[i].tabletData = tabletData;
if (!outWintabInfo.empty()) {
lastTabletData = outWintabInfo.back().tabletData;
}
return GHOST_kSuccess;
}
void GHOST_WindowWin32::updateWintabEvents()
GHOST_TabletData GHOST_WindowWin32::getLastTabletData()
{
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;
}
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]);
}
}
/* 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()
{
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()

View File

@ -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;
@ -437,13 +437,6 @@ 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.
* \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.
@ -461,16 +454,27 @@ class GHOST_WindowWin32 : public GHOST_Window {
WPARAM wParam,
LPARAM lParam);
/**
* Enables or disables Wintab context.
* \param enable: Whether the context should be enabled.
*/
void setWintabEnabled(bool enable);
/**
* Changes Wintab context overlap.
* \param overlap: Whether context should be brought to top of overlap order.
*/
void setWintabOverlap(bool overlap);
/**
* 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.
* Get the most recent tablet data.
* \return Most recent tablet data.
*/
void updateWintabEventsSyncTime();
/**
* Updates pending Wintab events.
*/
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;