/*++
    Copyright (c) 2000 Microsoft Corporation.  All rights reserved.

    Module Name:
        digidev.cpp

    Abstract:
        This module contains all the digitizer functions.

    Author:
        Michael Tsang (MikeTs) 01-Jun-2000

    Environment:
        User mode

    Revision History:
--*/

#include "pch.h"

/*++
    @doc    INTERNAL

    @func   BOOL | GetMinMax | Get the logical min & max for a report values.

    @parm   IN USAGE | UsagePage | Specifies the UsagePage of the report.
    @parm   IN USAGE | Usage | Specifies the Usage of the report.
    @parm   OUT PULONG | pulMin | To hold the min value.
    @parm   OUT PULONG | pulMax | To hold the max value.

    @rvalue SUCCESS | Returns TRUE.
    @rvalue FAILURE | Returns FALSE.
--*/

BOOL
GetMinMax(
    IN  USAGE  UsagePage,
    IN  USAGE  Usage,
    OUT PULONG pulMin,
    OUT PULONG pulMax
    )
{
    TRACEPROC("GetMinMax", 3)
    BOOL rc = FALSE;
    NTSTATUS status;
    HIDP_VALUE_CAPS ValueCaps;
    USHORT cValueCaps = 1;

    TRACEENTER(("(UsagePage=%x,Usage=%x,pulMin=%p,pulMax=%p)\n",
                UsagePage, Usage, pulMin, pulMax));

    status = HidP_GetSpecificValueCaps(HidP_Input,
                                       UsagePage,
                                       0,
                                       Usage,
                                       &ValueCaps,
                                       &cValueCaps,
                                       gdevDigitizer.pPreParsedData);

    if ((status == HIDP_STATUS_SUCCESS) && (cValueCaps == 1))
    {
        if ((ValueCaps.LogicalMin >= 0) &&
            (ValueCaps.LogicalMax > ValueCaps.LogicalMin))
        {
            *pulMin = ValueCaps.LogicalMin;
            *pulMax = ValueCaps.LogicalMax;
            rc = TRUE;
        }
    }

    TRACEEXIT(("=%x\n", rc));
    return rc;
}       //GetMinMax

/*++
    @doc    INTERNAL

    @func   VOID | ProcessDigitizerReport | Process a digitizer input report.

    @parm   IN PCHAR | pBuff | Buffer containing report data.

    @rvalue None.
--*/

VOID
ProcessDigitizerReport(
    IN PCHAR pBuff
    )
{
    TRACEPROC("ProcessDigitizerReport", 5)
    NTSTATUS StatusX, StatusY, StatusBtn;
    DWORD dwCurrentTime;
    ULONG x, y;
    ULONG ulLength;

    TRACEENTER(("(pBuff=%p)\n", pBuff));

    dwCurrentTime = GetTickCount();
    StatusX = HidP_GetUsageValue(HidP_Input,
                                 HID_USAGE_PAGE_GENERIC,
                                 0,
                                 HID_USAGE_GENERIC_X,
                                 &x,
                                 gdevDigitizer.pPreParsedData,
                                 pBuff,
                                 gdevDigitizer.hidCaps.InputReportByteLength);

    StatusY = HidP_GetUsageValue(HidP_Input,
                                 HID_USAGE_PAGE_GENERIC,
                                 0,
                                 HID_USAGE_GENERIC_Y,
                                 &y,
                                 gdevDigitizer.pPreParsedData,
                                 pBuff,
                                 gdevDigitizer.hidCaps.InputReportByteLength);

    ulLength = gdevDigitizer.dwcButtons;// ulLength == max # possible buttons
    StatusBtn = HidP_GetButtons(HidP_Input,
                                HID_USAGE_PAGE_DIGITIZER,
                                0,
                                gdevDigitizer.pDownButtonUsages,
                                &ulLength,
                                gdevDigitizer.pPreParsedData,
                                pBuff,
                                gdevDigitizer.hidCaps.InputReportByteLength);

    if ((StatusX == HIDP_STATUS_SUCCESS) &&
        (StatusY == HIDP_STATUS_SUCCESS) &&
        (StatusBtn == HIDP_STATUS_SUCCESS))
    {
        ULONG i;
        WORD wCurrentButtons = 0;

        for (i = 0; i < ulLength; i++)
        {
            if (gdevDigitizer.pDownButtonUsages[i] ==
                HID_USAGE_DIGITIZER_TIP_SWITCH)
            {
                wCurrentButtons |= TIP_SWITCH;
            }
            else if (gdevDigitizer.pDownButtonUsages[i] ==
                     HID_USAGE_DIGITIZER_BARREL_SWITCH)
            {
                wCurrentButtons |= BARREL_SWITCH;
            }
        }

        if ((gdwPenState == PENSTATE_PENDOWN) &&
            (dwCurrentTime - gdwPenDownTime >
             (DWORD)gConfig.GestureSettings.iPressHoldTime))
        {
            //
            // PressHold timer has expired, simulate a right down.
            //
            PressHoldMode(TRUE);
        }
        else if ((gdwPenState == PENSTATE_PRESSHOLD) &&
                 (dwCurrentTime - gdwPenDownTime >
                  (DWORD)gConfig.GestureSettings.iPressHoldTime +
                         gConfig.GestureSettings.iCancelPressHoldTime))
        {
            TRACEINFO(3, ("Simulate a left-down on CancelPressHold timeout.\n"));
            gdwPenState = PENSTATE_NORMAL;
            SetPressHoldCursor(FALSE);
            gInput.mi.dwFlags = MOUSEEVENTF_ABSOLUTE |
                                MOUSEEVENTF_MOVE |
                                SWAPBUTTONS(giButtonsSwapped,
                                            MOUSEEVENTF_LEFTDOWN,
                                            MOUSEEVENTF_RIGHTDOWN);
            gInput.mi.dx = glPenDownX;
            gInput.mi.dy = glPenDownY;
            SendInput(1, &gInput, sizeof(INPUT));
        }
        else if ((gdwPenState == PENSTATE_LEFTUP_PENDING) &&
                 (dwCurrentTime - gdwPenUpTime > TIMEOUT_LEFTCLICK))
        {
            //
            // Left up timer has expired, simulate a left up.
            //
            TRACEINFO(3, ("Simulate a left-up on timeout.\n"));
            gdwPenState = PENSTATE_NORMAL;
            gInput.mi.dwFlags = MOUSEEVENTF_ABSOLUTE |
                                MOUSEEVENTF_MOVE |
                                SWAPBUTTONS(giButtonsSwapped,
                                            MOUSEEVENTF_LEFTUP,
                                            MOUSEEVENTF_RIGHTUP);
            gInput.mi.dx = glPenUpX;
            gInput.mi.dy = glPenUpY;
            SendInput(1, &gInput, sizeof(INPUT));
        }

        //
        // If the report is the same as last, skip it.
        //
        if ((x != gLastRawDigiReport.wX) ||
            (y != gLastRawDigiReport.wY) ||
            (wCurrentButtons != gLastRawDigiReport.wButtonState))
        {
            //
            // Generate mouse movement info.
            //
            if (gdwfTabSrv & TSF_HAS_LINEAR_MAP)
            {
                gInput.mi.dx = x;
                gInput.mi.dy = y;
                AdjustLinearity((PUSHORT)&gInput.mi.dx, (PUSHORT)&gInput.mi.dy);
            }
            else
            {
                ULONG t;

                // Since we treat the digitizer as an 'absolute' pointing
                // device, both the X/Y coordinates must be scaled to a range
                // of 0 - 65535.
                // (((t = x - gdwMinX) << 16) - t)  == ((x - gdwMinX) * 65535)
                // In non-raw mode, the pen driver should already be scaled to
                // the range of 0-65535, but in raw mode, we still have to do
                // this work.

                gInput.mi.dx = (x >= gdwMaxX)?
                                MAX_NORMALIZED_X:       // too high
                               ((x <= gdwMinX)?
                                0:                      // too low
                                ((((t = x - gdwMinX) << 16) - t) / gdwRngX));

                gInput.mi.dy = (y >= gdwMaxY)?
                                MAX_NORMALIZED_Y :      // too high
                               ((y <= gdwMinY)?
                                0:                      // too low
                                ((((t = y - gdwMinY) << 16) - t) / gdwRngY));
            }

            gInput.mi.dx = (((gInput.mi.dx > glLongOffset)?
                             gInput.mi.dx - glLongOffset: 0)*NUM_PIXELS_LONG)/
                           max(gcxPrimary, gcyPrimary);
            gInput.mi.dy = (((gInput.mi.dy > glShortOffset)?
                             gInput.mi.dy - glShortOffset: 0)*NUM_PIXELS_SHORT)/
                           min(gcxPrimary, gcyPrimary);

            if (gdwfTabSrv & TSF_PORTRAIT_MODE)
            {
                LONG temp;

                if (gdwfTabSrv & TSF_PORTRAIT_MODE2)
                {
                    temp = gInput.mi.dx;
                    gInput.mi.dx = MAX_NORMALIZED_Y - gInput.mi.dy;
                }
                else
                {
                    temp = MAX_NORMALIZED_X - gInput.mi.dx;
                    gInput.mi.dx = gInput.mi.dy;
                }
                gInput.mi.dy = temp;
            }

            //
            // Pen tilt compensation.
            //
            gInput.mi.dx += gConfig.PenTilt.dx;
            gInput.mi.dy += gConfig.PenTilt.dy;

            NotifyClient(RawPtEvent,
                         wCurrentButtons,
                         (y << 16) | (x & 0xffff));

            ProcessMouseEvent(gInput.mi.dx,
                              gInput.mi.dy,
                              wCurrentButtons,
                              dwCurrentTime,
                              FALSE);

#ifdef DRAW_INK
            if (ghwndDrawInk != NULL)
            {
                static BOOL fPenDown = FALSE;
                static POINT ptPrev = {0, 0};
                static HPEN hpen = NULL;
                HDC hdc;

                if (!(gwLastButtons & TIP_SWITCH) &&
                     (wCurrentButtons & TIP_SWITCH))
                {
                    hpen = CreatePen(PS_SOLID, 0, RGB(255, 255, 255));
                    fPenDown = TRUE;
                    ptPrev.x = NORMAL_TO_SCREEN_X(gInput.mi.dx);
                    ptPrev.y = NORMAL_TO_SCREEN_Y(gInput.mi.dy);
                }
                else if ((gwLastButtons & TIP_SWITCH) &&
                         !(wCurrentButtons & TIP_SWITCH))
                {
                    DeleteObject(hpen);
                    hpen = NULL;
                    fPenDown = FALSE;
                }
                else if (fPenDown)
                {
                    HPEN hpenOld;
                    POINT pt;

                    pt.x = NORMAL_TO_SCREEN_X(gInput.mi.dx);
                    pt.y = NORMAL_TO_SCREEN_Y(gInput.mi.dy);
                    hdc = GetDC(ghwndDrawInk);
                    hpenOld = (HPEN)SelectObject(hdc, hpen);
                    MoveToEx(hdc, ptPrev.x, ptPrev.y, NULL);
                    LineTo(hdc, pt.x, pt.y);
                    SelectObject(hdc, hpenOld);
                    ReleaseDC(ghwndDrawInk, hdc);
                    ptPrev = pt;
                }
            }
#endif

            gwLastButtons = wCurrentButtons;
            gLastRawDigiReport.wX = (WORD)x;
            gLastRawDigiReport.wY = (WORD)y;
            gLastRawDigiReport.wButtonState = wCurrentButtons;
            gLastRawDigiReport.dwTime = dwCurrentTime;
        }
    }
    else
    {
        TABSRVERR(("failed getting data (StatusX=%d,StatusY=%d,StatusBtn=%d,Len=%d)\n",
                   StatusX, StatusY, StatusBtn, ulLength));
    }

    TRACEEXIT(("!\n"));
    return;
}       //ProcessDigitizerReport

/*++
    @doc    INTERNAL

    @func   VOID | AdjustLinearity | Adjust data according to the linearity map.

    @parm   IN OUT PUSHORT | pwX | Points to the X data.
    @parm   IN OUT PUSHORT | pwY | Points to the Y data.

    @rvalue None.
--*/

VOID
AdjustLinearity(
    IN OUT PUSHORT pwX,
    IN OUT PUSHORT pwY
    )
{
    TRACEPROC("AdjustLinearity", 5)
    int ix, iy, dix, diy, n;
    DIGIRECT Rect;
    LONG x, y;

    TRACEENTER(("(x=%d,y=%d)\n", *pwX, *pwY));

    for (n = 0, ix = gixIndex, iy = giyIndex; ; ix += dix, iy += diy, n++)
    {
        //
        // Safe guard from never-ending loop.
        //
        TRACEASSERT(n <= NUM_LINEAR_XPTS + NUM_LINEAR_YPTS);
        if (n > NUM_LINEAR_XPTS + NUM_LINEAR_YPTS)
        {
            TABSRVERR(("AdjustLinearity caught in a loop.\n"));
            break;
        }

        if ((*pwX < gConfig.LinearityMap.Data[iy][ix].wDigiPtX) &&
            (*pwX < gConfig.LinearityMap.Data[iy+1][ix].wDigiPtX))
        {
            dix = ix? -1: 0;
        }
        else if ((*pwX >= gConfig.LinearityMap.Data[iy][ix+1].wDigiPtX) &&
                 (*pwX >= gConfig.LinearityMap.Data[iy+1][ix+1].wDigiPtX))
        {
            dix = (ix + 2 < NUM_LINEAR_XPTS)? 1: 0;
        }
        else
        {
            dix = 0;
        }

        if ((*pwY < gConfig.LinearityMap.Data[iy][ix].wDigiPtY) &&
            (*pwY < gConfig.LinearityMap.Data[iy][ix+1].wDigiPtY))
        {
            diy = iy? -1: 0;
        }
        else if ((*pwY >= gConfig.LinearityMap.Data[iy+1][ix].wDigiPtY) &&
                 (*pwY >= gConfig.LinearityMap.Data[iy+1][ix+1].wDigiPtY))
        {
            diy = (iy + 2 < NUM_LINEAR_YPTS)? 1: 0;
        }
        else
        {
            diy = 0;
        }
        //
        // If delta-X and delta-Y are both zeros, we found the containing
        // rectangle.
        //
        if ((dix == 0) && (diy == 0))
        {
            break;
        }
    }

    TRACEASSERT(gConfig.LinearityMap.Data[iy+1][ix].wDigiPtY -
                gConfig.LinearityMap.Data[iy][ix].wDigiPtY);
    TRACEASSERT(gConfig.LinearityMap.Data[iy+1][ix+1].wDigiPtY -
                gConfig.LinearityMap.Data[iy][ix+1].wDigiPtY);
    Rect.wx0 = gConfig.LinearityMap.Data[iy][ix].wDigiPtX +
               ((*pwY - gConfig.LinearityMap.Data[iy][ix].wDigiPtY)*
                (gConfig.LinearityMap.Data[iy+1][ix].wDigiPtX -
                 gConfig.LinearityMap.Data[iy][ix].wDigiPtX))/
               (gConfig.LinearityMap.Data[iy+1][ix].wDigiPtY -
                gConfig.LinearityMap.Data[iy][ix].wDigiPtY);
    Rect.wx1 = gConfig.LinearityMap.Data[iy][ix+1].wDigiPtX +
               ((*pwY - gConfig.LinearityMap.Data[iy][ix+1].wDigiPtY)*
                (gConfig.LinearityMap.Data[iy+1][ix+1].wDigiPtX -
                 gConfig.LinearityMap.Data[iy][ix+1].wDigiPtX))/
               (gConfig.LinearityMap.Data[iy+1][ix+1].wDigiPtY -
                gConfig.LinearityMap.Data[iy][ix+1].wDigiPtY);

    TRACEASSERT(gConfig.LinearityMap.Data[iy][ix+1].wDigiPtX -
                gConfig.LinearityMap.Data[iy][ix].wDigiPtX);
    TRACEASSERT(gConfig.LinearityMap.Data[iy+1][ix+1].wDigiPtX -
                gConfig.LinearityMap.Data[iy+1][ix].wDigiPtX);
    Rect.wy0 = gConfig.LinearityMap.Data[iy][ix].wDigiPtY +
               ((*pwX - gConfig.LinearityMap.Data[iy][ix].wDigiPtX)*
                (gConfig.LinearityMap.Data[iy][ix+1].wDigiPtY -
                 gConfig.LinearityMap.Data[iy][ix].wDigiPtY))/
               (gConfig.LinearityMap.Data[iy][ix+1].wDigiPtX -
                gConfig.LinearityMap.Data[iy][ix].wDigiPtX);
    Rect.wy1 = gConfig.LinearityMap.Data[iy+1][ix].wDigiPtY +
               ((*pwX - gConfig.LinearityMap.Data[iy+1][ix].wDigiPtX)*
                (gConfig.LinearityMap.Data[iy+1][ix+1].wDigiPtY -
                 gConfig.LinearityMap.Data[iy+1][ix].wDigiPtY))/
               (gConfig.LinearityMap.Data[iy+1][ix+1].wDigiPtX -
                gConfig.LinearityMap.Data[iy+1][ix].wDigiPtX);

    //
    // Remember the upper-left corner of the containing rectangle so that
    // we will start with this rectangle the next time.
    //
    gixIndex = ix;
    giyIndex = iy;

    //
    // Calculate the adjustment:
    // x = x0Ref + (xDigi - x0Digi)*(x1Ref - x0Ref)/(x1Digi - x0Digi)
    // y = y0Ref + (yDigi - y0Digi)*(y1Ref - y0Ref)/(y1Digi - y1Digi)
    //
    TRACEASSERT((Rect.wx1 - Rect.wx0) != 0);
    TRACEASSERT((Rect.wy1 - Rect.wy0) != 0);
    x = gConfig.LinearityMap.Data[iy][ix].wRefPtX +
        ((*pwX - Rect.wx0)*
         (gConfig.LinearityMap.Data[iy][ix + 1].wRefPtX -
          gConfig.LinearityMap.Data[iy][ix].wRefPtX))/
        (Rect.wx1 - Rect.wx0);
    *pwX = (USHORT)((x < 0)? 0:
                    (x > MAX_NORMALIZED_X)? MAX_NORMALIZED_X: x);
    y = gConfig.LinearityMap.Data[iy][ix].wRefPtY +
        ((*pwY - Rect.wy0)*
         (gConfig.LinearityMap.Data[iy + 1][ix].wRefPtY -
          gConfig.LinearityMap.Data[iy][ix].wRefPtY))/
        (Rect.wy1 - Rect.wy0);
    *pwY = (USHORT)((y < 0)? 0:
                    (y > MAX_NORMALIZED_Y)? MAX_NORMALIZED_Y: y);

    TRACEEXIT(("!(x=%d,y=%d)\n", *pwX, *pwY));
    return;
}       //AdjustLinearity

/*++
    @doc    INTERNAL

    @func   LRESULT | ProcessMouseEvent | Process the mouse event.

    @parm   IN LONG | x | Current X.
    @parm   IN LONG | y | Current Y.
    @parm   IN WORD | wButtons | Current buttons state.
    @parm   IN DWORD | dwTime | Current time.
    @parm   IN BOOL | fLowLevelMouse | TRUE if called by LowLevelMouseProc.

    @rvalue SUCCESS | Returns non-zero to eat the mouse event.
    @rvalue FAILURE | Returns 0 to pass along the event to next handler.
--*/

LRESULT
ProcessMouseEvent(
    IN     LONG  x,
    IN     LONG  y,
    IN     WORD  wButtons,
    IN     DWORD dwTime,
    IN     BOOL  fLowLevelMouse
    )
{
    TRACEPROC("ProcessMouseEvent", 5)
    LRESULT rc = 0;
    DWORD dwEvent;

    TRACEENTER(("(x=%d,y=%d,Buttons=%x,Time=%d,fLLMouse=%x)\n",
                x, y, wButtons, dwTime, fLowLevelMouse));

    gInput.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
    if (fLowLevelMouse)
    {
        gInput.mi.dwFlags |= MOUSEEVENTF_VIRTUALDESK;
    }

    // Has the tip switch changed state?
    if (((gwLastButtons ^ wButtons) & TIP_SWITCH) != 0)
    {
        if (wButtons & TIP_SWITCH)
        {
            //
            // Tip switch is down.
            //
            if (gdwPenState == PENSTATE_LEFTUP_PENDING)
            {
                if (fLowLevelMouse)
                {
                    KillTimer(ghwndMouse, TIMERID_PRESSHOLD);
                }
                TRACEINFO(3, ("Simulate a left up on pen down.\n"));
                gdwPenState = PENSTATE_NORMAL;
                dwEvent = SWAPBUTTONS(giButtonsSwapped,
                                      MOUSEEVENTF_LEFTUP,
                                      MOUSEEVENTF_RIGHTUP);
                gInput.mi.dwFlags |= dwEvent;
                gInput.mi.dx = glPenUpX;
                gInput.mi.dy = glPenUpY;
                SendInput(1, &gInput, sizeof(INPUT));
                gInput.mi.dwFlags &= ~dwEvent;
                gInput.mi.dx = x;
                gInput.mi.dy = y;
            }

            if (gConfig.GestureSettings.dwfFeatures &
                GESTURE_FEATURE_PRESSHOLD_ENABLED)
            {
                if (CanDoPressHold(x, y))
                {
                    TRACEINFO(3, ("Pendown.\n"));
                    //
                    // Press and Hold is enabled, so hold back the left down event.
                    //
                    gdwPenDownTime = dwTime;
                    glPenDownX = x;
                    glPenDownY = y;
                    gdwPenState = PENSTATE_PENDOWN;
                    if (fLowLevelMouse)
                    {
                        SetTimer(ghwndMouse,
                                 TIMERID_PRESSHOLD,
                                 gConfig.GestureSettings.iPressHoldTime,
                                 NULL);
                    }
                    rc = 1;
                }
                else
                {
                    gInput.mi.dwFlags |= SWAPBUTTONS(giButtonsSwapped,
                                                     MOUSEEVENTF_LEFTDOWN,
                                                     MOUSEEVENTF_RIGHTDOWN);
                }
            }
            else
            {
                gInput.mi.dwFlags |= SWAPBUTTONS(giButtonsSwapped,
                                                 MOUSEEVENTF_LEFTDOWN,
                                                 MOUSEEVENTF_RIGHTDOWN);
            }
        }
        else
        {
            //
            // Tip switch is up.
            //
            if (gdwPenState == PENSTATE_PENDOWN)
            {
                //
                // If we still have pendown pending, it means presshold timer
                // has not expired, so we must cancel pendown pending and
                // simulate a left click.
                //
                TRACEINFO(3, ("Simulate a left-down.\n"));
                if (fLowLevelMouse)
                {
                    KillTimer(ghwndMouse, TIMERID_PRESSHOLD);
                }
                gdwPenState = PENSTATE_LEFTUP_PENDING;

                //
                // Simulate a left down.
                //
                gInput.mi.dwFlags |= SWAPBUTTONS(giButtonsSwapped,
                                                 MOUSEEVENTF_LEFTDOWN,
                                                 MOUSEEVENTF_RIGHTDOWN);
                gInput.mi.dx = glPenDownX;
                gInput.mi.dy = glPenDownY;
                SendInput(1, &gInput, sizeof(INPUT));

                gdwPenUpTime = dwTime;
                glPenUpX = x;
                glPenUpY = y;
                rc = 1;

                if (fLowLevelMouse)
                {
                    SetTimer(ghwndMouse,
                             TIMERID_PRESSHOLD,
                             TIMEOUT_LEFTCLICK,
                             NULL);
                }
            }
            else if ((gdwPenState == PENSTATE_PRESSHOLD) ||
                     (gdwPenState == PENSTATE_RIGHTDRAG))
            {
                if (gdwPenState == PENSTATE_PRESSHOLD)
                {
                    TRACEINFO(3, ("Simulate a right click.\n"));
                    SetPressHoldCursor(FALSE);
                    dwEvent = SWAPBUTTONS(giButtonsSwapped,
                                          MOUSEEVENTF_RIGHTDOWN,
                                          MOUSEEVENTF_LEFTDOWN);
                    gInput.mi.dwFlags |= dwEvent;
                    gInput.mi.dx = glPenDownX;
                    gInput.mi.dy = glPenDownY;
                    SendInput(1, &gInput, sizeof(INPUT));

                    gInput.mi.dwFlags &= ~dwEvent;
                    gInput.mi.dx = x;
                    gInput.mi.dy = y;
                }
                else
                {
                    TRACEINFO(3, ("Simulate a right up.\n"));
                }

                gInput.mi.dwFlags |= SWAPBUTTONS(giButtonsSwapped,
                                                 MOUSEEVENTF_RIGHTUP,
                                                 MOUSEEVENTF_LEFTUP);
                if (fLowLevelMouse)
                {
                    SendInput(1, &gInput, sizeof(INPUT));
                    rc = 1;
                }
                PressHoldMode(FALSE);
            }
            else
            {
                TRACEINFO(3, ("Do a left up.\n"));
                gInput.mi.dwFlags |= SWAPBUTTONS(giButtonsSwapped,
                                                 MOUSEEVENTF_LEFTUP,
                                                 MOUSEEVENTF_RIGHTUP);
            }
        }
    }
    else if (gdwPenState == PENSTATE_PENDOWN)
    {
        int cxScreen = fLowLevelMouse? gcxScreen: gcxPrimary,
            cyScreen = fLowLevelMouse? gcyScreen: gcyPrimary;

        if ((((abs(x - glPenDownX)*cxScreen) >> 16) >
             gConfig.GestureSettings.iHoldTolerance) ||
            (((abs(y - glPenDownY)*cyScreen) >> 16) >
             gConfig.GestureSettings.iHoldTolerance))
        {
            TRACEINFO(3, ("Cancel pen down pending, simulate a left down.\n"));
            if (fLowLevelMouse)
            {
                KillTimer(ghwndMouse, 1);
            }
            gdwPenState = PENSTATE_NORMAL;
            dwEvent = SWAPBUTTONS(giButtonsSwapped,
                                  MOUSEEVENTF_LEFTDOWN,
                                  MOUSEEVENTF_RIGHTDOWN);
            gInput.mi.dwFlags |= dwEvent;
            gInput.mi.dx = glPenDownX;
            gInput.mi.dy = glPenDownY;
            SendInput(1, &gInput, sizeof(INPUT));
            gInput.mi.dwFlags &= ~dwEvent;

            gInput.mi.dx = x;
            gInput.mi.dy = y;
        }
    }
    else if (gdwPenState == PENSTATE_PRESSHOLD)
    {
        int cxScreen = fLowLevelMouse? gcxScreen: gcxPrimary,
            cyScreen = fLowLevelMouse? gcyScreen: gcyPrimary;

        if ((((abs(x - glPenDownX)*cxScreen) >> 16) >
             gConfig.GestureSettings.iHoldTolerance) ||
            (((abs(y - glPenDownY)*cyScreen) >> 16) >
             gConfig.GestureSettings.iHoldTolerance))
        {
            KillTimer(ghwndMouse, TIMERID_PRESSHOLD);
            TRACEINFO(3, ("Simulate a right drag.\n"));
            SetPressHoldCursor(FALSE);
            gdwPenState = PENSTATE_RIGHTDRAG;

            dwEvent = SWAPBUTTONS(giButtonsSwapped,
                                  MOUSEEVENTF_RIGHTDOWN,
                                  MOUSEEVENTF_LEFTDOWN);
            gInput.mi.dwFlags |= dwEvent;
            gInput.mi.dx = glPenDownX;
            gInput.mi.dy = glPenDownY;
            SendInput(1, &gInput, sizeof(INPUT));
            gInput.mi.dwFlags &= ~dwEvent;

            gInput.mi.dx = x;
            gInput.mi.dy = y;
        }
    }
    else if (gdwPenState == PENSTATE_LEFTUP_PENDING)
    {
        //
        // Eat any movement before left-up timer expires.
        //
        rc = 1;
    }

    if (gConfig.GestureSettings.dwfFeatures & GESTURE_FEATURE_RECOG_ENABLED)
    {
        RecognizeGesture(gInput.mi.dx,
                         gInput.mi.dy,
                         wButtons,
                         dwTime,
                         fLowLevelMouse);
    }

    if (gdwfTabSrv & TSF_SUPERTIP_SENDINK)
    {
        LONG x, y;

        x = NORMAL_TO_SCREEN_X(gInput.mi.dx);
        y = NORMAL_TO_SCREEN_Y(gInput.mi.dy);
        PostMessage(ghwndSuperTIPInk,
                    guimsgSuperTIPInk,
                    gInput.mi.dwFlags,
                    (LPARAM)((y << 16) | (x & 0xffff)));
        if (gInput.mi.dwFlags & MOUSEEVENTF_LEFTUP)
        {
            gdwfTabSrv &= ~TSF_SUPERTIP_SENDINK;
        }
        rc = 1;
    }
    else if (!fLowLevelMouse && (rc == 0))
    {
        // Convert digitizer input to mouse input
        SendInput(1, &gInput, sizeof(INPUT));
    }

    TRACEEXIT(("=%x\n", rc));
    return rc;
}       //ProcessMouseEvent

/*++
    @doc    INTERNAL

    @func   VOID | PressHoldMode | Enable or disable Press and Hold mode.

    @parm   IN BOOL | fEnable | TRUE if entering Press and Hold mode.

    @rvalue None.
--*/

VOID
PressHoldMode(
    IN BOOL fEnable
    )
{
    TRACEPROC("PressHold", 3)
    TRACEENTER(("(fEnable=%x)\n", fEnable));

    if (fEnable)
    {
        TRACEINFO(3, ("Entering Press and Hold mode.\n"));

        SetPressHoldCursor(TRUE);
        gdwPenState = PENSTATE_PRESSHOLD;
    }
    else
    {
        TRACEINFO(3, ("Exiting Press and Hold mode.\n"));
        gdwPenState = PENSTATE_NORMAL;
    }

    TRACEEXIT(("!\n"));
    return;
}       //PressHoldMode

/*++
    @doc    INTERNAL

    @func   VOID | SetPressHoldCursor | Set Press and Hold cursor.

    @parm   IN BOOL | fPressHold | TRUE to set press and hold cursor.

    @rvalue None.
--*/

VOID
SetPressHoldCursor(
    IN BOOL fPressHold
    )
{
    TRACEPROC("SetPressHoldCursor", 3)
//    BOOL rc = FALSE;
//    static HCURSOR hcurPrev = NULL;
    static POINT pt = {0, 0};
    HDC hDC;
    HRGN hrgn;

    TRACEENTER(("(fPressHold=%x)\n", fPressHold));

    if (fPressHold)
    {
        GetCursorPos(&pt);
    }

    hDC = GetDC(NULL);
    TRACEASSERT(hDC != NULL);
    if (hDC != NULL)
    {
        hrgn = CreateEllipticRgn(pt.x - 10, pt.y - 10, pt.x + 10, pt.y + 10);
        TRACEASSERT(hrgn != NULL);
        if (hrgn != NULL)
        {
            InvertRgn(hDC, hrgn);
            DeleteObject(hrgn);
        }
        ReleaseDC(NULL, hDC);
    }

#if 0
    if (fPressHold)
    {
#if 0
        hcurPrev = (HCURSOR)LoadImage(NULL,
                                      MAKEINTRESOURCE(OCR_NORMAL),
                                      IMAGE_CURSOR,
                                      0,
                                      0,
                                      LR_SHARED | LR_DEFAULTSIZE);
        TRACEASSERT(hcurPrev != NULL);
#endif
        rc = SetSystemCursor(CopyCursor(ghcurPressHold), OCR_NORMAL);
        TRACEASSERT(rc == TRUE);
    }
    else
    {
#if 0
        if (hcurPrev != NULL)
        {
            SetCursor(hcurPrev);
            rc = SetSystemCursor(CopyCursor(hcurPrev), OCR_NORMAL);
            TRACEASSERT(rc == TRUE);
            hcurPrev = NULL;
        }
        else
        {
            TABSRVERR(("Failed to restore normal cursor (err=%d.\n",
                       GetLastError()));
        }
#endif
//#if 0
        LONG rcReg;
        HKEY hkUser, hkey;
        HCURSOR hcursor = NULL;

        rcReg = RegOpenCurrentUser(KEY_READ, &hkUser);
        if (rcReg == ERROR_SUCCESS)
        {
            rcReg = RegOpenKey(hkUser, REGSTR_PATH_CURSORS, &hkey);
            if (rcReg == ERROR_SUCCESS)
            {
                TCHAR szFile[MAX_PATH];
                DWORD dwcb = sizeof(szFile);

                rcReg = RegQueryValueEx(hkey,
                                        TEXT("Arrow"),
                                        NULL,
                                        NULL,
                                        (LPBYTE)szFile,
                                        &dwcb);
                if (rcReg == ERROR_SUCCESS)
                {
                    TRACEINFO(1, ("CursorArrow=%s\n", szFile));
                    hcursor = (HCURSOR)LoadImage(NULL,
                                                 MakeFileName(szFile),
                                                 IMAGE_CURSOR,
                                                 0,
                                                 0,
                                                 LR_LOADFROMFILE | LR_DEFAULTSIZE);
                }
                else
                {
                    TRACEWARN(("Failed to read Arrow registry value (rcReg=%x)\n",
                               rcReg));
                }
                RegCloseKey(hkey);
            }
            else
            {
                TRACEWARN(("Failed to open cursor registry key (rcReg=%x).\n",
                           rcReg));
            }
            RegCloseKey(hkUser);
        }
        else
        {
            TRACEWARN(("Failed to open current user (rcReg=%x).\n", rcReg));
        }

        if (hcursor == NULL)
        {
            hcursor = CopyCursor(ghcurNormal);
        }

        TRACEASSERT(hcursor != NULL);
        if (hcursor != NULL)
        {
            rc = SetSystemCursor(hcursor, OCR_NORMAL);
        }
        else
        {
            TABSRVERR(("Failed to restore system cursor.\n"));
        }
//#endif
    }
#endif

    TRACEEXIT(("!\n"));
    return;
}       //SetPressHoldCursor

/*++
    @doc    INTERNAL

    @func   LPTSTR | MakeFileName | Make a valid path by doing environment
            substitution.

    @parm   IN OUT LPTSTR | szFile | Points to the file name string.

    @rvalue returns szFile.
--*/

LPTSTR
MakeFileName(
    IN OUT LPTSTR pszFile
    )
{
    TRACEPROC("MakeFileName", 3)
    TCHAR szTmp[MAX_PATH];

    TRACEENTER(("(File=%s)\n", pszFile));

    ExpandEnvironmentStrings(pszFile, szTmp, ARRAYSIZE(szTmp));
    if ((szTmp[0] == TEXT('\\')) || (szTmp[1] == TEXT(':')))
    {
        lstrcpy(pszFile, szTmp);
    }
    else
    {
        GetSystemDirectory(pszFile, MAX_PATH);
        lstrcat(pszFile, TEXT("\\"));
        lstrcat(pszFile, szTmp);
    }

    TRACEEXIT(("! (File=%s)\n", pszFile));
    return pszFile;
}       //MakeFileName

/*++
    @doc    INTERNAL

    @func   BOOL | CanDoPressHold | Check if we can do press and hold.

    @parm   IN LONG | x | Cursor X.
    @parm   IN LONG | y | Cursor Y.

    @rvalue SUCCESS | Returns TRUE.
    @rvalue FAILURE | Returns FALSE.
--*/

BOOL
CanDoPressHold(
    IN LONG x,
    IN LONG y
    )
{
    TRACEPROC("CanDoPressHold", 3)
    BOOL rc = TRUE;

    TRACEENTER(("(x=%d,y=%d)\n", x, y));

    if (gpISuperTip != NULL)
    {
        __try
        {
            TIP_HIT_TEST_TYPE HitType;
            POINT pt;

            pt.x = NORMAL_TO_SCREEN_X(x);
            pt.y = NORMAL_TO_SCREEN_Y(y);

            if (SUCCEEDED(gpISuperTip->HitTest(pt, &HitType)) &&
                (HitType != TIP_HT_NONE) && (HitType != TIP_HT_STAGE))
            {
                TRACEINFO(3, ("Cursor is on HitType=%x\n", HitType));
                rc = FALSE;
                if ((HitType == TIP_HT_INK_AREA) && (ghwndSuperTIPInk != NULL))
                {
                    gdwfTabSrv |= TSF_SUPERTIP_SENDINK;
                }
            }
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            TABSRVERR(("Exception in SuperTIP HitTest (%x).\n",
                       _exception_code()));
        }
    }

    TRACEEXIT(("=%x\n", rc));
    return rc;
}       //CanDoPressHold