forked from bartvdbraak/blender
Refactor of Wintab to use Wintab supplied mouse movement once verified against system input.
Reviewed By: brecht, LazyDodo Maniphest Tasks: T88852 Differential Revision: https://developer.blender.org/D11508
This commit is contained in:
parent
445d506ac9
commit
6f158f834d
@ -370,6 +370,7 @@ elseif(WIN32)
|
||||
intern/GHOST_DropTargetWin32.cpp
|
||||
intern/GHOST_SystemWin32.cpp
|
||||
intern/GHOST_WindowWin32.cpp
|
||||
intern/GHOST_Wintab.cpp
|
||||
|
||||
intern/GHOST_ContextD3D.h
|
||||
intern/GHOST_DisplayManagerWin32.h
|
||||
@ -377,6 +378,7 @@ elseif(WIN32)
|
||||
intern/GHOST_SystemWin32.h
|
||||
intern/GHOST_TaskbarWin32.h
|
||||
intern/GHOST_WindowWin32.h
|
||||
intern/GHOST_Wintab.h
|
||||
)
|
||||
|
||||
if(NOT WITH_GL_EGL)
|
||||
|
@ -105,7 +105,9 @@ typedef enum {
|
||||
|
||||
typedef enum {
|
||||
GHOST_kTabletAutomatic = 0,
|
||||
GHOST_kTabletNative,
|
||||
/* Show as Windows Ink to users to match "Use Windows Ink" in tablet utilities, but we use the
|
||||
dependent Windows Pointer API. */
|
||||
GHOST_kTabletWinPointer,
|
||||
GHOST_kTabletWintab,
|
||||
} GHOST_TTabletAPI;
|
||||
|
||||
|
@ -239,7 +239,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) override;
|
||||
GHOST_TTabletAPI getTabletAPI(void);
|
||||
|
||||
#ifdef WITH_INPUT_NDOF
|
||||
|
@ -866,15 +866,151 @@ 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->getTabletData();
|
||||
|
||||
/* Move mouse to button event position. */
|
||||
if (window->getTabletData().Active != GHOST_kTabletModeNone) {
|
||||
/* Tablet should be handling inbetween mouse moves, only move to event position. */
|
||||
DWORD msgPos = ::GetMessagePos();
|
||||
int msgPosX = GET_X_LPARAM(msgPos);
|
||||
int msgPosY = GET_Y_LPARAM(msgPos);
|
||||
system->pushEvent(new GHOST_EventCursor(
|
||||
::GetMessageTime(), GHOST_kEventCursorMove, window, msgPosX, msgPosY, td));
|
||||
}
|
||||
|
||||
return new GHOST_EventButton(
|
||||
system->getMilliSeconds(), type, window, mask, window->getTabletData());
|
||||
window->updateMouseCapture(type == GHOST_kEventButtonDown ? MousePressed : MouseReleased);
|
||||
return new GHOST_EventButton(system->getMilliSeconds(), type, window, mask, td);
|
||||
}
|
||||
|
||||
void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window)
|
||||
{
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (!wt) {
|
||||
return;
|
||||
}
|
||||
|
||||
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
|
||||
|
||||
std::vector<GHOST_WintabInfoWin32> wintabInfo;
|
||||
wt->getInput(wintabInfo);
|
||||
|
||||
/* Wintab provided coordinates are untrusted until a Wintab and Win32 button down event match.
|
||||
* This is checked on every button down event, and revoked if there is a mismatch. This can
|
||||
* happen when Wintab incorrectly scales cursor position or is in mouse mode.
|
||||
*
|
||||
* If Wintab was never trusted while processing this Win32 event, a fallback Ghost cursor move
|
||||
* event is created at the position of the Win32 WT_PACKET event. */
|
||||
bool mouseMoveHandled;
|
||||
bool useWintabPos;
|
||||
mouseMoveHandled = useWintabPos = wt->trustCoordinates();
|
||||
|
||||
for (GHOST_WintabInfoWin32 &info : wintabInfo) {
|
||||
switch (info.type) {
|
||||
case GHOST_kEventCursorMove: {
|
||||
if (!useWintabPos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wt->mapWintabToSysCoordinates(info.x, info.y, info.x, info.y);
|
||||
system->pushEvent(new GHOST_EventCursor(
|
||||
info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
|
||||
|
||||
break;
|
||||
}
|
||||
case GHOST_kEventButtonDown: {
|
||||
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;
|
||||
}
|
||||
|
||||
/* Wintab buttons are modal, but the API does not inform us what mode a pressed button is
|
||||
* in. Only issue button events if we can steal an equivalent Win32 button event from the
|
||||
* event queue. */
|
||||
MSG msg;
|
||||
if (PeekMessage(&msg, window->getHWND(), message, message, PM_NOYIELD) &&
|
||||
msg.message != WM_QUIT) {
|
||||
|
||||
/* Test for Win32/Wintab button down match. */
|
||||
useWintabPos = wt->testCoordinates(msg.pt.x, msg.pt.y, info.x, info.y);
|
||||
if (!useWintabPos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Steal the Win32 event which was previously peeked. */
|
||||
PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD);
|
||||
|
||||
/* Move cursor to button location, to prevent incorrect cursor position when
|
||||
* transitioning from unsynchronized Win32 to Wintab cursor control. */
|
||||
wt->mapWintabToSysCoordinates(info.x, info.y, info.x, info.y);
|
||||
system->pushEvent(new GHOST_EventCursor(
|
||||
info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
|
||||
|
||||
window->updateMouseCapture(MousePressed);
|
||||
system->pushEvent(
|
||||
new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
|
||||
|
||||
mouseMoveHandled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
case GHOST_kEventButtonUp: {
|
||||
if (!useWintabPos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Wintab buttons are modal, but the API does not inform us what mode a pressed button is
|
||||
* in. Only issue button events if we can steal an equivalent Win32 button event from the
|
||||
* event queue. */
|
||||
MSG msg;
|
||||
if (PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD) &&
|
||||
msg.message != WM_QUIT) {
|
||||
|
||||
window->updateMouseCapture(MouseReleased);
|
||||
system->pushEvent(
|
||||
new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback cursor movement if Wintab position were never trusted while processing this event. */
|
||||
if (!mouseMoveHandled) {
|
||||
DWORD pos = GetMessagePos();
|
||||
int x = GET_X_LPARAM(pos);
|
||||
int y = GET_Y_LPARAM(pos);
|
||||
|
||||
/* TODO supply tablet data */
|
||||
system->pushEvent(new GHOST_EventCursor(
|
||||
system->getMilliSeconds(), GHOST_kEventCursorMove, window, x, y, GHOST_TABLET_DATA_NONE));
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_SystemWin32::processPointerEvent(
|
||||
@ -882,7 +1018,7 @@ void GHOST_SystemWin32::processPointerEvent(
|
||||
{
|
||||
/* 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)) {
|
||||
if (!window->usingTabletAPI(GHOST_kTabletWinPointer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -893,20 +1029,21 @@ void GHOST_SystemWin32::processPointerEvent(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pointerInfo[0].isPrimary) {
|
||||
eventHandled = true;
|
||||
return; // For multi-touch displays we ignore these events
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case WM_POINTERENTER:
|
||||
window->m_tabletInRange = true;
|
||||
system->pushEvent(new GHOST_EventCursor(pointerInfo[0].time,
|
||||
GHOST_kEventCursorMove,
|
||||
window,
|
||||
pointerInfo[0].pixelLocation.x,
|
||||
pointerInfo[0].pixelLocation.y,
|
||||
pointerInfo[0].tabletData));
|
||||
case WM_POINTERUPDATE:
|
||||
/* Coalesced pointer events are reverse chronological order, reorder chronologically.
|
||||
* Only contiguous move events are coalesced. */
|
||||
for (GHOST_TUns32 i = pointerInfo.size(); i-- > 0;) {
|
||||
system->pushEvent(new GHOST_EventCursor(pointerInfo[i].time,
|
||||
GHOST_kEventCursorMove,
|
||||
window,
|
||||
pointerInfo[i].pixelLocation.x,
|
||||
pointerInfo[i].pixelLocation.y,
|
||||
pointerInfo[i].tabletData));
|
||||
}
|
||||
|
||||
/* Leave event unhandled so that system cursor is moved. */
|
||||
|
||||
break;
|
||||
case WM_POINTERDOWN:
|
||||
/* Move cursor to point of contact because GHOST_EventButton does not include position. */
|
||||
@ -922,18 +1059,10 @@ void GHOST_SystemWin32::processPointerEvent(
|
||||
pointerInfo[0].buttonMask,
|
||||
pointerInfo[0].tabletData));
|
||||
window->updateMouseCapture(MousePressed);
|
||||
break;
|
||||
case WM_POINTERUPDATE:
|
||||
/* Coalesced pointer events are reverse chronological order, reorder chronologically.
|
||||
* Only contiguous move events are coalesced. */
|
||||
for (GHOST_TUns32 i = pointerInfo.size(); i-- > 0;) {
|
||||
system->pushEvent(new GHOST_EventCursor(pointerInfo[i].time,
|
||||
GHOST_kEventCursorMove,
|
||||
window,
|
||||
pointerInfo[i].pixelLocation.x,
|
||||
pointerInfo[i].pixelLocation.y,
|
||||
pointerInfo[i].tabletData));
|
||||
}
|
||||
|
||||
/* Mark event handled so that mouse button events are not generated. */
|
||||
eventHandled = true;
|
||||
|
||||
break;
|
||||
case WM_POINTERUP:
|
||||
system->pushEvent(new GHOST_EventButton(pointerInfo[0].time,
|
||||
@ -942,16 +1071,14 @@ void GHOST_SystemWin32::processPointerEvent(
|
||||
pointerInfo[0].buttonMask,
|
||||
pointerInfo[0].tabletData));
|
||||
window->updateMouseCapture(MouseReleased);
|
||||
break;
|
||||
case WM_POINTERLEAVE:
|
||||
window->m_tabletInRange = false;
|
||||
|
||||
/* Mark event handled so that mouse button events are not generated. */
|
||||
eventHandled = true;
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
eventHandled = true;
|
||||
system->setCursorPosition(pointerInfo[0].pixelLocation.x, pointerInfo[0].pixelLocation.y);
|
||||
}
|
||||
|
||||
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
|
||||
@ -959,18 +1086,14 @@ 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)) {
|
||||
/* 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 (window->getTabletData().Active != GHOST_kTabletModeNone) {
|
||||
/* While pen devices are in range, cursor movement is handled by tablet input processing. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
@ -982,7 +1105,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
|
||||
}
|
||||
|
||||
/* Could also clamp to screen bounds wrap with a window outside the view will fail atm.
|
||||
* Use offset of 8 in case the window is at screen bounds. */
|
||||
* Use inset in case the window is at screen bounds. */
|
||||
bounds.wrapPoint(x_new, y_new, 2, window->getCursorGrabAxis());
|
||||
|
||||
window->getCursorGrabAccum(x_accum, y_accum);
|
||||
@ -998,7 +1121,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 {
|
||||
@ -1007,7 +1130,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
|
||||
window,
|
||||
x_screen,
|
||||
y_screen,
|
||||
window->getTabletData());
|
||||
GHOST_TABLET_DATA_NONE);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@ -1117,6 +1240,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)
|
||||
{
|
||||
@ -1124,7 +1264,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);
|
||||
@ -1152,6 +1291,31 @@ 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);
|
||||
|
||||
/* If API is set to WinPointer (Windows Ink), unload Wintab so that trouble drivers don't disable
|
||||
* Windows Ink. Load Wintab when API is Automatic because decision logic relies on knowing
|
||||
* whether a Wintab device is present. */
|
||||
const bool loadWintab = GHOST_kTabletWinPointer != api;
|
||||
GHOST_WindowManager *wm = getWindowManager();
|
||||
|
||||
for (GHOST_IWindow *win : wm->getWindows()) {
|
||||
GHOST_WindowWin32 *windowWin32 = (GHOST_WindowWin32 *)win;
|
||||
if (loadWintab) {
|
||||
windowWin32->loadWintab(GHOST_kWindowStateMinimized != windowWin32->getState());
|
||||
|
||||
if (windowWin32->usingTabletAPI(GHOST_kTabletWintab)) {
|
||||
windowWin32->resetPointerPenInfo();
|
||||
}
|
||||
}
|
||||
else {
|
||||
windowWin32->closeWintab();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_SystemWin32::processMinMaxInfo(MINMAXINFO *minmax)
|
||||
{
|
||||
minmax->ptMinTrackSize.x = 320;
|
||||
@ -1386,33 +1550,100 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
case SC_KEYMENU:
|
||||
eventHandled = true;
|
||||
break;
|
||||
case SC_RESTORE:
|
||||
case SC_RESTORE: {
|
||||
::ShowWindow(hwnd, SW_RESTORE);
|
||||
window->setState(window->getState());
|
||||
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
wt->enable();
|
||||
}
|
||||
|
||||
eventHandled = true;
|
||||
break;
|
||||
}
|
||||
case SC_MAXIMIZE: {
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
wt->enable();
|
||||
}
|
||||
/* Don't report event as handled so that default handling occurs. */
|
||||
break;
|
||||
}
|
||||
case SC_MINIMIZE: {
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
wt->disable();
|
||||
}
|
||||
/* Don't report event as handled so that default handling occurs. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Wintab events, processed
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
case WT_PACKET:
|
||||
window->processWin32TabletEvent(wParam, lParam);
|
||||
case WT_CSRCHANGE: {
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
wt->updateCursorInfo();
|
||||
}
|
||||
eventHandled = true;
|
||||
break;
|
||||
case WT_CSRCHANGE:
|
||||
case WT_PROXIMITY:
|
||||
window->processWin32TabletInitEvent();
|
||||
}
|
||||
case WT_PROXIMITY: {
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
bool inRange = LOWORD(lParam);
|
||||
if (inRange) {
|
||||
/* Some devices don't emit WT_CSRCHANGE events, so update cursor info here. */
|
||||
wt->updateCursorInfo();
|
||||
}
|
||||
else {
|
||||
wt->leaveRange();
|
||||
}
|
||||
}
|
||||
eventHandled = true;
|
||||
break;
|
||||
}
|
||||
case WT_INFOCHANGE: {
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
wt->processInfoChange(lParam);
|
||||
|
||||
if (window->usingTabletAPI(GHOST_kTabletWintab)) {
|
||||
window->resetPointerPenInfo();
|
||||
}
|
||||
}
|
||||
eventHandled = true;
|
||||
break;
|
||||
}
|
||||
case WT_PACKET:
|
||||
processWintabEvent(window);
|
||||
eventHandled = true;
|
||||
break;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Pointer events, processed
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
case WM_POINTERENTER:
|
||||
case WM_POINTERDOWN:
|
||||
case WM_POINTERUPDATE:
|
||||
case WM_POINTERDOWN:
|
||||
case WM_POINTERUP:
|
||||
case WM_POINTERLEAVE:
|
||||
processPointerEvent(msg, window, wParam, lParam, eventHandled);
|
||||
break;
|
||||
case WM_POINTERLEAVE: {
|
||||
GHOST_TUns32 pointerId = GET_POINTERID_WPARAM(wParam);
|
||||
POINTER_INFO pointerInfo;
|
||||
if (!GetPointerInfo(pointerId, &pointerInfo)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Reset pointer pen info if pen device has left tracking range. */
|
||||
if (pointerInfo.pointerType == PT_PEN && !IS_POINTER_INRANGE_WPARAM(wParam)) {
|
||||
window->resetPointerPenInfo();
|
||||
eventHandled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Mouse events, processed
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
@ -1451,7 +1682,20 @@ 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;
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
wt->gainFocus();
|
||||
}
|
||||
}
|
||||
|
||||
event = processCursorEvent(window);
|
||||
|
||||
break;
|
||||
case WM_MOUSEWHEEL: {
|
||||
/* The WM_MOUSEWHEEL message is sent to the focus window
|
||||
@ -1486,7 +1730,17 @@ 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;
|
||||
if (window->getTabletData().Active == GHOST_kTabletModeNone) {
|
||||
processCursorEvent(window);
|
||||
}
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
wt->loseFocus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Mouse events, ignored
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
@ -1534,7 +1788,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
* will not be dispatched to OUR active window if we minimize one of OUR windows. */
|
||||
if (LOWORD(wParam) == WA_INACTIVE)
|
||||
window->lostMouseCapture();
|
||||
window->processWin32TabletActivateEvent(GET_WM_ACTIVATE_STATE(wParam, lParam));
|
||||
|
||||
lResult = ::DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
break;
|
||||
}
|
||||
@ -1576,6 +1830,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
|
||||
@ -1583,15 +1839,7 @@ 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);
|
||||
break;
|
||||
case WM_CAPTURECHANGED:
|
||||
window->lostMouseCapture();
|
||||
@ -1642,6 +1890,24 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
break;
|
||||
case WM_DISPLAYCHANGE: {
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
for (GHOST_IWindow *iter_win : system->getWindowManager()->getWindows()) {
|
||||
GHOST_WindowWin32 *iter_win32win = (GHOST_WindowWin32 *)iter_win;
|
||||
wt->remapCoordinates();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_KILLFOCUS:
|
||||
/* The WM_KILLFOCUS message is sent to a window immediately before it loses the keyboard
|
||||
* focus. We want to prevent this if a window is still active and it loses focus to
|
||||
* nowhere. */
|
||||
if (!wParam && hwnd == ::GetActiveWindow()) {
|
||||
::SetFocus(hwnd);
|
||||
}
|
||||
break;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Window events, ignored
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
@ -1678,12 +1944,6 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
* object associated with the window.
|
||||
*/
|
||||
break;
|
||||
case WM_KILLFOCUS:
|
||||
/* The WM_KILLFOCUS message is sent to a window immediately before it loses the
|
||||
* keyboard focus. We want to prevent this if a window is still active and it loses
|
||||
* focus to nowhere. */
|
||||
if (!wParam && hwnd == ::GetActiveWindow())
|
||||
::SetFocus(hwnd);
|
||||
case WM_SHOWWINDOW:
|
||||
/* The WM_SHOWWINDOW message is sent to a window when the window is
|
||||
* about to be hidden or shown. */
|
||||
|
@ -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.
|
||||
@ -308,6 +318,12 @@ class GHOST_SystemWin32 : public GHOST_System {
|
||||
GHOST_WindowWin32 *window,
|
||||
GHOST_TButtonMask mask);
|
||||
|
||||
/**
|
||||
* Creates tablet events from Wintab events.
|
||||
* \param window: The window receiving the event (the active window).
|
||||
*/
|
||||
static void processWintabEvent(GHOST_WindowWin32 *window);
|
||||
|
||||
/**
|
||||
* Creates tablet events from pointer events.
|
||||
* \param type: The type of pointer event.
|
||||
@ -351,6 +367,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.
|
||||
|
@ -21,8 +21,6 @@
|
||||
* \ingroup GHOST
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include "GHOST_WindowWin32.h"
|
||||
#include "GHOST_ContextD3D.h"
|
||||
#include "GHOST_ContextNone.h"
|
||||
@ -72,7 +70,7 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
|
||||
bool is_debug,
|
||||
bool dialog)
|
||||
: GHOST_Window(width, height, state, wantStereoVisual, false),
|
||||
m_tabletInRange(false),
|
||||
m_mousePresent(false),
|
||||
m_inLiveResize(false),
|
||||
m_system(system),
|
||||
m_hDC(0),
|
||||
@ -82,6 +80,8 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
|
||||
m_nPressedButtons(0),
|
||||
m_customCursor(0),
|
||||
m_wantAlphaBackground(alphaBackground),
|
||||
m_wintab(NULL),
|
||||
m_lastPointerTabletData(GHOST_TABLET_DATA_NONE),
|
||||
m_normal_state(GHOST_kWindowStateNormal),
|
||||
m_user32(NULL),
|
||||
m_parentWindowHwnd(parentwindow ? parentwindow->m_hWnd : HWND_DESKTOP),
|
||||
@ -90,10 +90,6 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
|
||||
wchar_t *title_16 = alloc_utf16_from_8((char *)title, 0);
|
||||
RECT win_rect = {left, top, (long)(left + width), (long)(top + height)};
|
||||
|
||||
// Initialize tablet variables
|
||||
memset(&m_wintab, 0, sizeof(m_wintab));
|
||||
m_tabletData = GHOST_TABLET_DATA_NONE;
|
||||
|
||||
DWORD style = parentwindow ?
|
||||
WS_POPUPWINDOW | WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX :
|
||||
WS_OVERLAPPEDWINDOW;
|
||||
@ -218,65 +214,10 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
|
||||
}
|
||||
|
||||
// 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 don't 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 (system->getTabletAPI() != GHOST_kTabletWinPointer) {
|
||||
loadWintab(GHOST_kWindowStateMinimized != state);
|
||||
}
|
||||
|
||||
CoCreateInstance(
|
||||
CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (LPVOID *)&m_Bar);
|
||||
}
|
||||
@ -289,14 +230,7 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
|
||||
m_Bar = NULL;
|
||||
}
|
||||
|
||||
if (m_wintab.handle) {
|
||||
if (m_wintab.close && m_wintab.tablet) {
|
||||
m_wintab.close(m_wintab.tablet);
|
||||
}
|
||||
|
||||
FreeLibrary(m_wintab.handle);
|
||||
memset(&m_wintab, 0, sizeof(m_wintab));
|
||||
}
|
||||
closeWintab();
|
||||
|
||||
if (m_user32) {
|
||||
FreeLibrary(m_user32);
|
||||
@ -913,20 +847,16 @@ GHOST_TSuccess GHOST_WindowWin32::hasCursorShape(GHOST_TStandardCursor cursorSha
|
||||
GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(
|
||||
std::vector<GHOST_PointerInfoWin32> &outPointerInfo, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (!useTabletAPI(GHOST_kTabletNative)) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
GHOST_TInt32 pointerId = GET_POINTERID_WPARAM(wParam);
|
||||
GHOST_TInt32 isPrimary = IS_POINTER_PRIMARY_WPARAM(wParam);
|
||||
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem();
|
||||
GHOST_TUns32 outCount;
|
||||
GHOST_TUns32 outCount = 0;
|
||||
|
||||
if (!(GetPointerInfoHistory(pointerId, &outCount, NULL))) {
|
||||
if (!(GetPointerPenInfoHistory(pointerId, &outCount, NULL))) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
auto pointerPenInfo = std::vector<POINTER_PEN_INFO>(outCount);
|
||||
std::vector<POINTER_PEN_INFO> pointerPenInfo(outCount);
|
||||
outPointerInfo.resize(outCount);
|
||||
|
||||
if (!(GetPointerPenInfoHistory(pointerId, &outCount, pointerPenInfo.data()))) {
|
||||
@ -988,148 +918,77 @@ GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(
|
||||
}
|
||||
}
|
||||
|
||||
if (!outPointerInfo.empty()) {
|
||||
m_lastPointerTabletData = outPointerInfo.back().tabletData;
|
||||
}
|
||||
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
void GHOST_WindowWin32::processWin32TabletActivateEvent(WORD state)
|
||||
void GHOST_WindowWin32::resetPointerPenInfo()
|
||||
{
|
||||
if (!useTabletAPI(GHOST_kTabletWintab)) {
|
||||
return;
|
||||
}
|
||||
m_lastPointerTabletData = GHOST_TABLET_DATA_NONE;
|
||||
}
|
||||
|
||||
if (m_wintab.enable && m_wintab.tablet) {
|
||||
m_wintab.enable(m_wintab.tablet, state);
|
||||
GHOST_Wintab *GHOST_WindowWin32::getWintab() const
|
||||
{
|
||||
return m_wintab;
|
||||
}
|
||||
|
||||
if (m_wintab.overlap && state) {
|
||||
m_wintab.overlap(m_wintab.tablet, TRUE);
|
||||
void GHOST_WindowWin32::loadWintab(bool enable)
|
||||
{
|
||||
if (!m_wintab) {
|
||||
if (m_wintab = GHOST_Wintab::loadWintab(m_hWnd)) {
|
||||
if (enable) {
|
||||
m_wintab->enable();
|
||||
|
||||
/* Focus Wintab if cursor is inside this window. This ensures Wintab is enabled when the
|
||||
* tablet is used to change the Tablet API. */
|
||||
GHOST_TInt32 x, y;
|
||||
if (m_system->getCursorPosition(x, y)) {
|
||||
GHOST_Rect rect;
|
||||
getClientBounds(rect);
|
||||
|
||||
if (rect.isInside(x, y)) {
|
||||
m_wintab->gainFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_WindowWin32::useTabletAPI(GHOST_TTabletAPI api) const
|
||||
void GHOST_WindowWin32::closeWintab()
|
||||
{
|
||||
delete m_wintab;
|
||||
m_wintab = NULL;
|
||||
}
|
||||
|
||||
bool GHOST_WindowWin32::usingTabletAPI(GHOST_TTabletAPI api) const
|
||||
{
|
||||
if (m_system->getTabletAPI() == api) {
|
||||
return true;
|
||||
}
|
||||
else if (m_system->getTabletAPI() == GHOST_kTabletAutomatic) {
|
||||
if (m_wintab.tablet)
|
||||
if (m_wintab && m_wintab->devicesPresent()) {
|
||||
return api == GHOST_kTabletWintab;
|
||||
else
|
||||
return api == GHOST_kTabletNative;
|
||||
}
|
||||
else {
|
||||
return api == GHOST_kTabletWinPointer;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_WindowWin32::processWin32TabletInitEvent()
|
||||
GHOST_TabletData GHOST_WindowWin32::getTabletData()
|
||||
{
|
||||
if (!useTabletAPI(GHOST_kTabletWintab)) {
|
||||
return;
|
||||
if (usingTabletAPI(GHOST_kTabletWintab)) {
|
||||
return m_wintab ? m_wintab->getLastTabletData() : GHOST_TABLET_DATA_NONE;
|
||||
}
|
||||
|
||||
// Let's see if we can initialize tablet here
|
||||
if (m_wintab.info && m_wintab.tablet) {
|
||||
AXIS Pressure, Orientation[3]; /* The maximum tablet size */
|
||||
|
||||
BOOL pressureSupport = m_wintab.info(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
|
||||
if (pressureSupport)
|
||||
m_wintab.maxPressure = Pressure.axMax;
|
||||
else
|
||||
m_wintab.maxPressure = 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 don't do tilt stuff. */
|
||||
m_wintab.maxAzimuth = m_wintab.maxAltitude = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_tabletData.Active = GHOST_kTabletModeNone;
|
||||
}
|
||||
|
||||
void GHOST_WindowWin32::processWin32TabletEvent(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (!useTabletAPI(GHOST_kTabletWintab)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.maxPressure > 0) {
|
||||
m_tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_wintab.maxPressure;
|
||||
}
|
||||
else {
|
||||
m_tabletData.Pressure = 1.0f;
|
||||
}
|
||||
|
||||
if ((m_wintab.maxAzimuth > 0) && (m_wintab.maxAltitude > 0)) {
|
||||
ORIENTATION ort = pkt.pkOrientation;
|
||||
float vecLen;
|
||||
float altRad, azmRad; /* in radians */
|
||||
|
||||
/*
|
||||
* from the wintab spec:
|
||||
* orAzimuth Specifies the clockwise rotation of the
|
||||
* cursor about the z axis through a full circular range.
|
||||
*
|
||||
* orAltitude Specifies the angle with the x-y plane
|
||||
* through a signed, semicircular range. Positive values
|
||||
* specify an angle upward toward the positive z axis;
|
||||
* negative values specify an angle downward toward the negative z axis.
|
||||
*
|
||||
* wintab.h defines .orAltitude as a UINT but documents .orAltitude
|
||||
* as positive for upward angles and negative for downward angles.
|
||||
* WACOM uses negative altitude values to show that the pen is inverted;
|
||||
* therefore we cast .orAltitude as an (int) and then use the absolute value.
|
||||
*/
|
||||
|
||||
/* convert raw fixed point data to radians */
|
||||
altRad = (float)((fabs((float)ort.orAltitude) / (float)m_wintab.maxAltitude) * M_PI / 2.0);
|
||||
azmRad = (float)(((float)ort.orAzimuth / (float)m_wintab.maxAzimuth) * M_PI * 2.0);
|
||||
|
||||
/* find length of the stylus' projected vector on the XY plane */
|
||||
vecLen = cos(altRad);
|
||||
|
||||
/* from there calculate X and Y components based on azimuth */
|
||||
m_tabletData.Xtilt = sin(azmRad) * vecLen;
|
||||
m_tabletData.Ytilt = (float)(sin(M_PI / 2.0 - azmRad) * vecLen);
|
||||
}
|
||||
else {
|
||||
m_tabletData.Xtilt = 0.0f;
|
||||
m_tabletData.Ytilt = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_WindowWin32::bringTabletContextToFront()
|
||||
{
|
||||
if (!useTabletAPI(GHOST_kTabletWintab)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_wintab.overlap && m_wintab.tablet) {
|
||||
m_wintab.overlap(m_wintab.tablet, TRUE);
|
||||
else {
|
||||
return m_lastPointerTabletData;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,29 +30,16 @@
|
||||
|
||||
#include "GHOST_TaskbarWin32.h"
|
||||
#include "GHOST_Window.h"
|
||||
#include "GHOST_Wintab.h"
|
||||
#ifdef WITH_INPUT_IME
|
||||
# include "GHOST_ImeWin32.h"
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <wintab.h>
|
||||
// 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)
|
||||
#define PACKETMODE PK_BUTTONS
|
||||
#include <pktdef.h>
|
||||
|
||||
class GHOST_SystemWin32;
|
||||
class GHOST_DropTargetWin32;
|
||||
|
||||
// typedefs for WinTab functions to allow dynamic loading
|
||||
typedef UINT(API *GHOST_WIN32_WTInfo)(UINT, UINT, LPVOID);
|
||||
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_WTEnable)(HCTX, BOOL);
|
||||
typedef BOOL(API *GHOST_WIN32_WTOverlap)(HCTX, BOOL);
|
||||
|
||||
// typedefs for user32 functions to allow dynamic loading of Windows 10 DPI scaling functions
|
||||
typedef UINT(API *GHOST_WIN32_GetDpiForWindow)(HWND);
|
||||
|
||||
@ -62,7 +49,6 @@ struct GHOST_PointerInfoWin32 {
|
||||
GHOST_TButtonMask buttonMask;
|
||||
POINT pixelLocation;
|
||||
GHOST_TUns64 time;
|
||||
|
||||
GHOST_TabletData tabletData;
|
||||
};
|
||||
|
||||
@ -256,16 +242,11 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query whether given tablet API should be used.
|
||||
* \param api: Tablet API to test.
|
||||
*/
|
||||
bool useTabletAPI(GHOST_TTabletAPI api) const;
|
||||
bool usingTabletAPI(GHOST_TTabletAPI api) const;
|
||||
|
||||
/**
|
||||
* Translate WM_POINTER events into GHOST_PointerInfoWin32 structs.
|
||||
@ -278,10 +259,34 @@ class GHOST_WindowWin32 : public GHOST_Window {
|
||||
WPARAM wParam,
|
||||
LPARAM lParam);
|
||||
|
||||
void processWin32TabletActivateEvent(WORD state);
|
||||
void processWin32TabletInitEvent();
|
||||
void processWin32TabletEvent(WPARAM wParam, LPARAM lParam);
|
||||
void bringTabletContextToFront();
|
||||
/**
|
||||
* Resets pointer pen tablet state.
|
||||
*/
|
||||
void resetPointerPenInfo();
|
||||
|
||||
/**
|
||||
* Retrieves pointer to Wintab if Wintab is the set Tablet API.
|
||||
* \return Pointer to Wintab member.
|
||||
*/
|
||||
GHOST_Wintab *getWintab() const;
|
||||
|
||||
/**
|
||||
* Loads Wintab context for the window.
|
||||
* \param enable: True if Wintab should be enabled after loading. Wintab should not be enabled if
|
||||
* the window is minimzed.
|
||||
*/
|
||||
void loadWintab(bool enable);
|
||||
|
||||
/**
|
||||
* Closes Wintab for the window.
|
||||
*/
|
||||
void closeWintab();
|
||||
|
||||
/**
|
||||
* Get the most recent Windows Pointer tablet data.
|
||||
* \return Most recent pointer tablet data.
|
||||
*/
|
||||
GHOST_TabletData getTabletData();
|
||||
|
||||
GHOST_TSuccess beginFullScreen() const
|
||||
{
|
||||
@ -295,10 +300,10 @@ class GHOST_WindowWin32 : public GHOST_Window {
|
||||
|
||||
GHOST_TUns16 getDPIHint() override;
|
||||
|
||||
/** Whether a tablet stylus is being tracked. */
|
||||
bool m_tabletInRange;
|
||||
/** True if the mouse is either over or captured by the window. */
|
||||
bool m_mousePresent;
|
||||
|
||||
/** if the window currently resizing */
|
||||
/** True if the window currently resizing. */
|
||||
bool m_inLiveResize;
|
||||
|
||||
#ifdef WITH_INPUT_IME
|
||||
@ -382,27 +387,11 @@ 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;
|
||||
/** Pointer to Wintab manager if Wintab is loaded. */
|
||||
GHOST_Wintab *m_wintab;
|
||||
|
||||
/* Wintab API */
|
||||
struct {
|
||||
/** `WinTab.dll` 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;
|
||||
|
||||
/** Stores the Tablet context if detected Tablet features using `WinTab.dll` */
|
||||
HCTX tablet;
|
||||
LONG maxPressure;
|
||||
LONG maxAzimuth, maxAltitude;
|
||||
} m_wintab;
|
||||
/** Most recent tablet data. */
|
||||
GHOST_TabletData m_lastPointerTabletData;
|
||||
|
||||
GHOST_TWindowState m_normal_state;
|
||||
|
||||
|
491
intern/ghost/intern/GHOST_Wintab.cpp
Normal file
491
intern/ghost/intern/GHOST_Wintab.cpp
Normal file
@ -0,0 +1,491 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include "GHOST_Wintab.h"
|
||||
|
||||
GHOST_Wintab *GHOST_Wintab::loadWintab(HWND hwnd)
|
||||
{
|
||||
/* Load Wintab library if available. */
|
||||
|
||||
auto handle = unique_hmodule(::LoadLibrary("Wintab32.dll"), &::FreeLibrary);
|
||||
if (!handle) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Get Wintab functions. */
|
||||
|
||||
auto info = (GHOST_WIN32_WTInfo)::GetProcAddress(handle.get(), "WTInfoA");
|
||||
if (!info) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto open = (GHOST_WIN32_WTOpen)::GetProcAddress(handle.get(), "WTOpenA");
|
||||
if (!open) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto get = (GHOST_WIN32_WTGet)::GetProcAddress(handle.get(), "WTGetA");
|
||||
if (!get) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto set = (GHOST_WIN32_WTSet)::GetProcAddress(handle.get(), "WTSetA");
|
||||
if (!set) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto close = (GHOST_WIN32_WTClose)::GetProcAddress(handle.get(), "WTClose");
|
||||
if (!close) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto packetsGet = (GHOST_WIN32_WTPacketsGet)::GetProcAddress(handle.get(), "WTPacketsGet");
|
||||
if (!packetsGet) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto queueSizeGet = (GHOST_WIN32_WTQueueSizeGet)::GetProcAddress(handle.get(), "WTQueueSizeGet");
|
||||
if (!queueSizeGet) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto queueSizeSet = (GHOST_WIN32_WTQueueSizeSet)::GetProcAddress(handle.get(), "WTQueueSizeSet");
|
||||
if (!queueSizeSet) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto enable = (GHOST_WIN32_WTEnable)::GetProcAddress(handle.get(), "WTEnable");
|
||||
if (!enable) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto overlap = (GHOST_WIN32_WTOverlap)::GetProcAddress(handle.get(), "WTOverlap");
|
||||
if (!overlap) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Build Wintab context. */
|
||||
|
||||
LOGCONTEXT lc = {0};
|
||||
if (!info(WTI_DEFSYSCTX, 0, &lc)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Coord tablet, system;
|
||||
extractCoordinates(lc, tablet, system);
|
||||
modifyContext(lc);
|
||||
|
||||
/* The Wintab spec says we must open the context disabled if we are using cursor masks. */
|
||||
auto hctx = unique_hctx(open(hwnd, &lc, FALSE), close);
|
||||
if (!hctx) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* 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 = queueSizeGet(hctx.get());
|
||||
|
||||
while (queueSize < maxQueue) {
|
||||
int testSize = min(queueSize + 16, maxQueue);
|
||||
if (queueSizeSet(hctx.get(), 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. The Wintab spec dates back to 16 bit Windows, thus
|
||||
* assumes memory recently deallocated may not be available, which is no longer a practical
|
||||
* concern. */
|
||||
if (!queueSizeSet(hctx.get(), queueSize)) {
|
||||
/* If a previously valid queue size is no longer valid, there is likely something wrong in
|
||||
* the Wintab implementation and we should not use it. */
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new GHOST_Wintab(hwnd,
|
||||
std::move(handle),
|
||||
info,
|
||||
get,
|
||||
set,
|
||||
packetsGet,
|
||||
enable,
|
||||
overlap,
|
||||
std::move(hctx),
|
||||
tablet,
|
||||
system,
|
||||
queueSize);
|
||||
}
|
||||
|
||||
void GHOST_Wintab::modifyContext(LOGCONTEXT &lc)
|
||||
{
|
||||
lc.lcPktData = PACKETDATA;
|
||||
lc.lcPktMode = PACKETMODE;
|
||||
lc.lcMoveMask = PACKETDATA;
|
||||
lc.lcOptions |= CXO_CSRMESSAGES | CXO_MESSAGES;
|
||||
|
||||
/* Tablet scaling is handled manually because some drivers don't handle HIDPI or multi-display
|
||||
* correctly; reset tablet scale factors to unscaled tablet coodinates. */
|
||||
lc.lcOutOrgX = lc.lcInOrgX;
|
||||
lc.lcOutOrgY = lc.lcInOrgY;
|
||||
lc.lcOutExtX = lc.lcInExtX;
|
||||
lc.lcOutExtY = lc.lcInExtY;
|
||||
}
|
||||
|
||||
void GHOST_Wintab::extractCoordinates(LOGCONTEXT &lc, Coord &tablet, Coord &system)
|
||||
{
|
||||
tablet.x.org = lc.lcInOrgX;
|
||||
tablet.x.ext = lc.lcInExtX;
|
||||
tablet.y.org = lc.lcInOrgY;
|
||||
tablet.y.ext = lc.lcInExtY;
|
||||
|
||||
system.x.org = lc.lcSysOrgX;
|
||||
system.x.ext = lc.lcSysExtX;
|
||||
system.y.org = lc.lcSysOrgY;
|
||||
/* Wintab maps y origin to the tablet's bottom; invert y to match Windows y origin mapping to the
|
||||
* screen top. */
|
||||
system.y.ext = -lc.lcSysExtY;
|
||||
}
|
||||
|
||||
GHOST_Wintab::GHOST_Wintab(HWND hwnd,
|
||||
unique_hmodule handle,
|
||||
GHOST_WIN32_WTInfo info,
|
||||
GHOST_WIN32_WTGet get,
|
||||
GHOST_WIN32_WTSet set,
|
||||
GHOST_WIN32_WTPacketsGet packetsGet,
|
||||
GHOST_WIN32_WTEnable enable,
|
||||
GHOST_WIN32_WTOverlap overlap,
|
||||
unique_hctx hctx,
|
||||
Coord tablet,
|
||||
Coord system,
|
||||
int queueSize)
|
||||
: m_handle{std::move(handle)},
|
||||
m_fpInfo{info},
|
||||
m_fpGet{get},
|
||||
m_fpSet{set},
|
||||
m_fpPacketsGet{packetsGet},
|
||||
m_fpEnable{enable},
|
||||
m_fpOverlap{overlap},
|
||||
m_context{std::move(hctx)},
|
||||
m_tabletCoord{tablet},
|
||||
m_systemCoord{system},
|
||||
m_pkts{queueSize}
|
||||
{
|
||||
m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices);
|
||||
updateCursorInfo();
|
||||
}
|
||||
|
||||
void GHOST_Wintab::enable()
|
||||
{
|
||||
m_fpEnable(m_context.get(), true);
|
||||
m_enabled = true;
|
||||
}
|
||||
|
||||
void GHOST_Wintab::disable()
|
||||
{
|
||||
if (m_focused) {
|
||||
loseFocus();
|
||||
}
|
||||
m_fpEnable(m_context.get(), false);
|
||||
m_enabled = false;
|
||||
}
|
||||
|
||||
void GHOST_Wintab::gainFocus()
|
||||
{
|
||||
m_fpOverlap(m_context.get(), true);
|
||||
m_focused = true;
|
||||
}
|
||||
|
||||
void GHOST_Wintab::loseFocus()
|
||||
{
|
||||
if (m_lastTabletData.Active != GHOST_kTabletModeNone) {
|
||||
leaveRange();
|
||||
}
|
||||
|
||||
/* Mouse mode of tablet or display layout may change when Wintab or Window is inactive. Don't
|
||||
* trust for mouse movement until re-verified. */
|
||||
m_coordTrusted = false;
|
||||
|
||||
m_fpOverlap(m_context.get(), false);
|
||||
m_focused = false;
|
||||
}
|
||||
|
||||
void GHOST_Wintab::leaveRange()
|
||||
{
|
||||
/* Button state can't be tracked while out of range, reset it. */
|
||||
m_buttons = 0;
|
||||
/* Set to none to indicate tablet is inactive. */
|
||||
m_lastTabletData = GHOST_TABLET_DATA_NONE;
|
||||
/* Clear the packet queue. */
|
||||
m_fpPacketsGet(m_context.get(), m_pkts.size(), m_pkts.data());
|
||||
}
|
||||
|
||||
void GHOST_Wintab::remapCoordinates()
|
||||
{
|
||||
LOGCONTEXT lc = {0};
|
||||
|
||||
if (m_fpInfo(WTI_DEFSYSCTX, 0, &lc)) {
|
||||
extractCoordinates(lc, m_tabletCoord, m_systemCoord);
|
||||
modifyContext(lc);
|
||||
|
||||
m_fpSet(m_context.get(), &lc);
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_Wintab::updateCursorInfo()
|
||||
{
|
||||
AXIS Pressure, Orientation[3];
|
||||
|
||||
BOOL pressureSupport = m_fpInfo(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
|
||||
m_maxPressure = pressureSupport ? Pressure.axMax : 0;
|
||||
|
||||
BOOL tiltSupport = m_fpInfo(WTI_DEVICES, DVC_ORIENTATION, &Orientation);
|
||||
/* Check if tablet supports azimuth [0] and altitude [1], encoded in axResolution. */
|
||||
if (tiltSupport && Orientation[0].axResolution && Orientation[1].axResolution) {
|
||||
m_maxAzimuth = Orientation[0].axMax;
|
||||
m_maxAltitude = Orientation[1].axMax;
|
||||
}
|
||||
else {
|
||||
m_maxAzimuth = m_maxAltitude = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_Wintab::processInfoChange(LPARAM lParam)
|
||||
{
|
||||
/* Update number of connected Wintab digitizers. */
|
||||
if (LOWORD(lParam) == WTI_INTERFACE && HIWORD(lParam) == IFC_NDEVICES) {
|
||||
m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices);
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_Wintab::devicesPresent()
|
||||
{
|
||||
return m_numDevices > 0;
|
||||
}
|
||||
|
||||
GHOST_TabletData GHOST_Wintab::getLastTabletData()
|
||||
{
|
||||
return m_lastTabletData;
|
||||
}
|
||||
|
||||
void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
|
||||
{
|
||||
const int numPackets = m_fpPacketsGet(m_context.get(), m_pkts.size(), m_pkts.data());
|
||||
outWintabInfo.resize(numPackets);
|
||||
size_t outExtent = 0;
|
||||
|
||||
for (int i = 0; i < numPackets; i++) {
|
||||
PACKET pkt = m_pkts[i];
|
||||
GHOST_WintabInfoWin32 &out = outWintabInfo[i + outExtent];
|
||||
|
||||
out.tabletData = GHOST_TABLET_DATA_NONE;
|
||||
/* % 3 for multiple devices ("DualTrack"). */
|
||||
switch (pkt.pkCursor % 3) {
|
||||
case 0:
|
||||
/* Puck - processed as mouse. */
|
||||
out.tabletData.Active = GHOST_kTabletModeNone;
|
||||
break;
|
||||
case 1:
|
||||
out.tabletData.Active = GHOST_kTabletModeStylus;
|
||||
break;
|
||||
case 2:
|
||||
out.tabletData.Active = GHOST_kTabletModeEraser;
|
||||
break;
|
||||
}
|
||||
|
||||
out.x = pkt.pkX;
|
||||
out.y = pkt.pkY;
|
||||
|
||||
if (m_maxPressure > 0) {
|
||||
out.tabletData.Pressure = (float)pkt.pkNormalPressure / (float)m_maxPressure;
|
||||
}
|
||||
|
||||
if ((m_maxAzimuth > 0) && (m_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_maxAltitude) * M_PI / 2.0);
|
||||
azmRad = (float)(((float)ort.orAzimuth / (float)m_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. */
|
||||
out.tabletData.Xtilt = sin(azmRad) * vecLen;
|
||||
out.tabletData.Ytilt = (float)(sin(M_PI / 2.0 - azmRad) * vecLen);
|
||||
}
|
||||
|
||||
out.time = pkt.pkTime;
|
||||
|
||||
/* Some Wintab libraries don't handle relative button input, so we track button presses
|
||||
* manually. */
|
||||
out.button = GHOST_kButtonMaskNone;
|
||||
out.type = GHOST_kEventCursorMove;
|
||||
|
||||
DWORD buttonsChanged = m_buttons ^ pkt.pkButtons;
|
||||
WORD buttonIndex = 0;
|
||||
GHOST_WintabInfoWin32 buttonRef = out;
|
||||
int buttons = 0;
|
||||
|
||||
while (buttonsChanged) {
|
||||
if (buttonsChanged & 1) {
|
||||
/* Find the index for the changed button from the button map. */
|
||||
GHOST_TButtonMask button = mapWintabToGhostButton(pkt.pkCursor, buttonIndex);
|
||||
|
||||
if (button != GHOST_kButtonMaskNone) {
|
||||
/* Extend output if multiple buttons are pressed. We don't extend input until we confirm
|
||||
* a Wintab buttons maps to a system button. */
|
||||
if (buttons > 0) {
|
||||
outWintabInfo.resize(outWintabInfo.size() + 1);
|
||||
outExtent++;
|
||||
GHOST_WintabInfoWin32 &out = outWintabInfo[i + outExtent];
|
||||
out = buttonRef;
|
||||
}
|
||||
buttons++;
|
||||
|
||||
out.button = button;
|
||||
if (buttonsChanged & pkt.pkButtons) {
|
||||
out.type = GHOST_kEventButtonDown;
|
||||
}
|
||||
else {
|
||||
out.type = GHOST_kEventButtonUp;
|
||||
}
|
||||
}
|
||||
|
||||
m_buttons ^= 1 << buttonIndex;
|
||||
}
|
||||
|
||||
buttonsChanged >>= 1;
|
||||
buttonIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!outWintabInfo.empty()) {
|
||||
m_lastTabletData = outWintabInfo.back().tabletData;
|
||||
}
|
||||
}
|
||||
|
||||
GHOST_TButtonMask GHOST_Wintab::mapWintabToGhostButton(UINT cursor, WORD physicalButton)
|
||||
{
|
||||
const WORD numButtons = 32;
|
||||
BYTE logicalButtons[numButtons] = {0};
|
||||
BYTE systemButtons[numButtons] = {0};
|
||||
|
||||
if (!m_fpInfo(WTI_CURSORS + cursor, CSR_BUTTONMAP, &logicalButtons) ||
|
||||
!m_fpInfo(WTI_CURSORS + cursor, CSR_SYSBTNMAP, &systemButtons)) {
|
||||
return GHOST_kButtonMaskNone;
|
||||
}
|
||||
|
||||
if (physicalButton >= numButtons) {
|
||||
return GHOST_kButtonMaskNone;
|
||||
}
|
||||
|
||||
BYTE lb = logicalButtons[physicalButton];
|
||||
|
||||
if (lb >= numButtons) {
|
||||
return GHOST_kButtonMaskNone;
|
||||
}
|
||||
|
||||
switch (systemButtons[lb]) {
|
||||
case SBN_LCLICK:
|
||||
return GHOST_kButtonMaskLeft;
|
||||
case SBN_RCLICK:
|
||||
return GHOST_kButtonMaskRight;
|
||||
case SBN_MCLICK:
|
||||
return GHOST_kButtonMaskMiddle;
|
||||
default:
|
||||
return GHOST_kButtonMaskNone;
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_Wintab::mapWintabToSysCoordinates(int x_in, int y_in, int &x_out, int &y_out)
|
||||
{
|
||||
/* Maps from range [in.org, in.org + abs(in.ext)] to [out.org, out.org + abs(out.ext)], in
|
||||
* reverse if in.ext and out.ext have differing sign. */
|
||||
auto remap = [](int inPoint, Range in, Range out) -> int {
|
||||
int absInExt = abs(in.ext);
|
||||
int absOutExt = abs(out.ext);
|
||||
|
||||
/* Translate input from range [in.org, in.org + absInExt] to [0, absInExt] */
|
||||
int inMagnitude = inPoint - in.org;
|
||||
|
||||
/* If signs of extents differ, reverse input over range. */
|
||||
if ((in.ext < 0) != (out.ext < 0)) {
|
||||
inMagnitude = absInExt - inMagnitude;
|
||||
}
|
||||
|
||||
/* Scale from [0, absInExt] to [0, absOutExt]. */
|
||||
int outMagnitude = inMagnitude * absOutExt / absInExt;
|
||||
|
||||
/* Translate from range [0, absOutExt] to [out.org, out.org + absOutExt]. */
|
||||
int outPoint = outMagnitude + out.org;
|
||||
|
||||
return outPoint;
|
||||
};
|
||||
|
||||
x_out = remap(x_in, m_tabletCoord.x, m_systemCoord.x);
|
||||
y_out = remap(y_in, m_tabletCoord.y, m_systemCoord.y);
|
||||
}
|
||||
|
||||
bool GHOST_Wintab::trustCoordinates()
|
||||
{
|
||||
return m_coordTrusted;
|
||||
}
|
||||
|
||||
bool GHOST_Wintab::testCoordinates(int sysX, int sysY, int wtX, int wtY)
|
||||
{
|
||||
mapWintabToSysCoordinates(wtX, wtY, wtX, wtY);
|
||||
|
||||
/* Allow off by one pixel tolerance in case of rounding error. */
|
||||
if (abs(sysX - wtX) <= 1 && abs(sysY - wtY) <= 1) {
|
||||
m_coordTrusted = true;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
m_coordTrusted = false;
|
||||
return false;
|
||||
}
|
||||
}
|
250
intern/ghost/intern/GHOST_Wintab.h
Normal file
250
intern/ghost/intern/GHOST_Wintab.h
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
* Declaration of GHOST_WintabWin32 class.
|
||||
*/
|
||||
|
||||
/* Wacom's Wintab documentation is periodically offline, moved, and increasingly hidden away. You
|
||||
* can find a (painstakingly) archived copy of the documentation at
|
||||
* https://web.archive.org/web/20201122230125/https://developer-docs-legacy.wacom.com/display/DevDocs/Windows+Wintab+Documentation
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <wtypes.h>
|
||||
|
||||
#include "GHOST_Types.h"
|
||||
|
||||
#include <wintab.h>
|
||||
/* 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
|
||||
#include <pktdef.h>
|
||||
|
||||
/* 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 int(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);
|
||||
|
||||
/* Typedefs for Wintab and Windows resource management. */
|
||||
typedef std::unique_ptr<std::remove_pointer_t<HMODULE>, decltype(&::FreeLibrary)> unique_hmodule;
|
||||
typedef std::unique_ptr<std::remove_pointer_t<HCTX>, GHOST_WIN32_WTClose> unique_hctx;
|
||||
|
||||
struct GHOST_WintabInfoWin32 {
|
||||
GHOST_TInt32 x, y;
|
||||
GHOST_TEventType type;
|
||||
GHOST_TButtonMask button;
|
||||
GHOST_TUns64 time;
|
||||
GHOST_TabletData tabletData;
|
||||
};
|
||||
|
||||
class GHOST_Wintab {
|
||||
public:
|
||||
/**
|
||||
* Loads Wintab if available.
|
||||
* \param hwnd: Window to attach Wintab context to.
|
||||
*/
|
||||
static GHOST_Wintab *loadWintab(HWND hwnd);
|
||||
|
||||
/**
|
||||
* Enables Wintab context.
|
||||
*/
|
||||
void enable();
|
||||
|
||||
/**
|
||||
* Disables the Wintab context and unwinds Wintab state.
|
||||
*/
|
||||
void disable();
|
||||
|
||||
/**
|
||||
* Brings Wintab context to the top of the overlap order.
|
||||
*/
|
||||
void gainFocus();
|
||||
|
||||
/**
|
||||
* Puts Wintab context at bottom of overlap order and unwinds Wintab state.
|
||||
*/
|
||||
void loseFocus();
|
||||
|
||||
/**
|
||||
* Clean up when Wintab leaves tracking range.
|
||||
*/
|
||||
void leaveRange();
|
||||
|
||||
/**
|
||||
* Handle Wintab coordinate changes when DisplayChange events occur.
|
||||
*/
|
||||
void remapCoordinates();
|
||||
|
||||
/**
|
||||
* Maps Wintab to Win32 display coordinates.
|
||||
* \param x_in: The tablet x coordinate.
|
||||
* \param y_in: The tablet y coordinate.
|
||||
* \param x_out: Output for the Win32 mapped x coordinate.
|
||||
* \param y_out: Output for the Win32 mapped y coordiante.
|
||||
*/
|
||||
void mapWintabToSysCoordinates(int x_in, int y_in, int &x_out, int &y_out);
|
||||
|
||||
/**
|
||||
* Updates cached Wintab properties for current cursor.
|
||||
*/
|
||||
void updateCursorInfo();
|
||||
|
||||
/**
|
||||
* Handle Wintab info changes such as change in number of connected tablets.
|
||||
* \param lParam: LPARAM of the event.
|
||||
*/
|
||||
void processInfoChange(LPARAM lParam);
|
||||
|
||||
/**
|
||||
* Whether Wintab devices are present.
|
||||
* \return True if Wintab devices are present.
|
||||
*/
|
||||
bool devicesPresent();
|
||||
|
||||
/**
|
||||
* Translate Wintab packets into GHOST_WintabInfoWin32 structs.
|
||||
* \param outWintabInfo: Storage to return resulting GHOST_WintabInfoWin32 data.
|
||||
*/
|
||||
void getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo);
|
||||
|
||||
/**
|
||||
* Whether Wintab coordinates should be trusted.
|
||||
* \return True if Wintab coordinates should be trusted.
|
||||
*/
|
||||
bool trustCoordinates();
|
||||
|
||||
/**
|
||||
* Tests whether Wintab coordinates can be trusted by comparing Win32 and Wintab reported curser
|
||||
* position.
|
||||
* \param sysX: System cursor x position.
|
||||
* \param sysY: System cursor y position.
|
||||
* \param wtX: Wintab cursor x position.
|
||||
* \param wtY: Wintab cursor y position.
|
||||
* \return True if Win32 and Wintab cursor positions match within tolerance.
|
||||
*
|
||||
* Note: Only test coordiantes on button press, not release. This prevents issues when async
|
||||
* mismatch causes mouse movement to replay and snap back, which is only an issue while drawing.
|
||||
*/
|
||||
bool testCoordinates(int sysX, int sysY, int wtX, int wtY);
|
||||
|
||||
/**
|
||||
* Retrieve the most recent tablet data, or none if pen is not in range.
|
||||
* \return Most recent tablet data, or none if pen is not in range.
|
||||
*/
|
||||
GHOST_TabletData getLastTabletData();
|
||||
|
||||
private:
|
||||
/** Wintab dll handle. */
|
||||
unique_hmodule m_handle;
|
||||
/** Wintab API functions. */
|
||||
GHOST_WIN32_WTInfo m_fpInfo = nullptr;
|
||||
GHOST_WIN32_WTGet m_fpGet = nullptr;
|
||||
GHOST_WIN32_WTSet m_fpSet = nullptr;
|
||||
GHOST_WIN32_WTPacketsGet m_fpPacketsGet = nullptr;
|
||||
GHOST_WIN32_WTEnable m_fpEnable = nullptr;
|
||||
GHOST_WIN32_WTOverlap m_fpOverlap = nullptr;
|
||||
|
||||
/** Stores the Wintab tablet context. */
|
||||
unique_hctx m_context;
|
||||
/** Whether the context is enabled. */
|
||||
bool m_enabled = false;
|
||||
/** Whether the context has focus and is at the top of overlap order. */
|
||||
bool m_focused = false;
|
||||
|
||||
/** Pressed button map. */
|
||||
GHOST_TUns8 m_buttons = 0;
|
||||
|
||||
/** Range of a coodinate space. */
|
||||
struct Range {
|
||||
/** Origin of range. */
|
||||
int org = 0;
|
||||
/** Extent of range. */
|
||||
int ext = 1;
|
||||
};
|
||||
|
||||
/** 2D Coordinate space. */
|
||||
struct Coord {
|
||||
/** Range of x. */
|
||||
Range x = {};
|
||||
/** Range of y. */
|
||||
Range y = {};
|
||||
};
|
||||
/** Whether Wintab coordinates are trusted. */
|
||||
bool m_coordTrusted = false;
|
||||
/** Tablet input range. */
|
||||
Coord m_tabletCoord = {};
|
||||
/** System output range. */
|
||||
Coord m_systemCoord = {};
|
||||
|
||||
int m_maxPressure = 0;
|
||||
int m_maxAzimuth = 0;
|
||||
int m_maxAltitude = 0;
|
||||
|
||||
/** Number of connected Wintab devices. */
|
||||
UINT m_numDevices = 0;
|
||||
/** Reusable buffer to read in Wintab packets. */
|
||||
std::vector<PACKET> m_pkts;
|
||||
/** Most recently received tablet data, or none if pen is not in range. */
|
||||
GHOST_TabletData m_lastTabletData = GHOST_TABLET_DATA_NONE;
|
||||
|
||||
GHOST_Wintab(HWND hwnd,
|
||||
unique_hmodule handle,
|
||||
GHOST_WIN32_WTInfo info,
|
||||
GHOST_WIN32_WTGet get,
|
||||
GHOST_WIN32_WTSet set,
|
||||
GHOST_WIN32_WTPacketsGet packetsGet,
|
||||
GHOST_WIN32_WTEnable enable,
|
||||
GHOST_WIN32_WTOverlap overlap,
|
||||
unique_hctx hctx,
|
||||
Coord tablet,
|
||||
Coord system,
|
||||
int queueSize);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* \return The system mapped button.
|
||||
*/
|
||||
GHOST_TButtonMask mapWintabToGhostButton(UINT cursor, WORD physicalButton);
|
||||
|
||||
/**
|
||||
* Applies common modifications to Wintab context.
|
||||
* \param lc: Wintab context to modify.
|
||||
*/
|
||||
static void modifyContext(LOGCONTEXT &lc);
|
||||
|
||||
/**
|
||||
* Extracts tablet and system coordinates from Wintab context.
|
||||
* \param lc: Wintab context to extract coordinates from.
|
||||
* \param tablet: Tablet coordinates.
|
||||
* \param system: System coordinates.
|
||||
*/
|
||||
static void extractCoordinates(LOGCONTEXT &lc, Coord &tablet, Coord &system);
|
||||
};
|
@ -2080,7 +2080,7 @@ void WM_init_tablet_api(void)
|
||||
if (g_system) {
|
||||
switch (U.tablet_api) {
|
||||
case USER_TABLET_NATIVE:
|
||||
GHOST_SetTabletAPI(g_system, GHOST_kTabletNative);
|
||||
GHOST_SetTabletAPI(g_system, GHOST_kTabletWinPointer);
|
||||
break;
|
||||
case USER_TABLET_WINTAB:
|
||||
GHOST_SetTabletAPI(g_system, GHOST_kTabletWintab);
|
||||
|
Loading…
Reference in New Issue
Block a user