// winusbhidtest_app.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "targetver.h"
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <process.h>
#include <assert.h>
#include <winusb.h>
#include <setupapi.h>

#include <initguid.h>
// Interface GUID for PID=0xC408 & VID=0x046D target
// {A4919745-8D0F-47f1-82F9-EAA1AA18FFFF}
DEFINE_GUID(GUID_DEVINTERFACE_WINUSBHIDTESTTARGET, 
  0xA4919745, 0x8D0F, 0x47F1, 0x82, 0xF9, 0xEA, 0xA1, 0xAA, 0x18, 0xFF, 0xFF);

/*
 * 本当はインターフェースディスクリプタの wMaxPacketSize の値を見て
 * 適切な大きさのバッファを確保する方が良いのだが、今回は Interrupt-IN の
 * wMaxPacketSize はそれほど大きく無いと仮定。
 */
#define PIPE_BUFFER_SIZE 128

BOOL ThreadLoop = TRUE;
HANDLE hThread = NULL;
unsigned int ThreadId; 
HANDLE hUsb = INVALID_HANDLE_VALUE;
WINUSB_INTERFACE_HANDLE hWinUsb = 0;
UCHAR IntrPipeId = 0xffu;       // Interrupt-IN pipe ID
USHORT IntrPipeMaxPktSize = 0u; // Interrupt-IN maximum packet size
unsigned char IntrBuf[PIPE_BUFFER_SIZE] = { 0 };

// WinUsb_howto.docx 記載のプログラムを元に、
// 異常判定処理を追加。
BOOL GetDevicePath(LPGUID InterfaceGuid,
                   LPTSTR DevicePath,
                   size_t BufLen)
{
    BOOL bResult = TRUE;
    HDEVINFO hDeviceInfo = INVALID_HANDLE_VALUE;
    SP_DEVICE_INTERFACE_DATA interfaceData;
    PSP_DEVICE_INTERFACE_DETAIL_DATA pDetailData = NULL;
    ULONG requiredLength = 0;
    ULONG gotLength = 0;

// [1]
    hDeviceInfo = SetupDiGetClassDevs(
        InterfaceGuid, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (INVALID_HANDLE_VALUE == hDeviceInfo)
    {
        bResult = FALSE;
    }

// [2]
    if (bResult)
    {
        memset(&interfaceData, 0, sizeof(interfaceData));
        interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
        bResult = SetupDiEnumDeviceInterfaces(
            hDeviceInfo, NULL, InterfaceGuid, 0, &interfaceData);
    }

// [3]
    if (bResult)
    {
        bResult = SetupDiGetDeviceInterfaceDetail(
            hDeviceInfo, &interfaceData, NULL, 0, &requiredLength, NULL);
        if (!bResult && (ERROR_INSUFFICIENT_BUFFER == GetLastError()))
        {
            bResult = TRUE;
        }
        else
        {
            bResult = FALSE;
        }

    }
    if (bResult)
    {
        assert(0 < requiredLength);
        pDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)
            LocalAlloc(LMEM_FIXED, requiredLength);
        if (NULL == pDetailData)
        {
            bResult = FALSE;
        }
    }
    
    if (bResult)
    {
        memset(pDetailData, 0, requiredLength);
        pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
        bResult = SetupDiGetDeviceInterfaceDetail(
            hDeviceInfo, &interfaceData,
            pDetailData, requiredLength, &gotLength, NULL);
    }

// [4]
    if (bResult)
    {
        if (_tcscpy_s(DevicePath, BufLen, pDetailData->DevicePath))
        {
            bResult = FALSE;
        }
    }

    if (INVALID_HANDLE_VALUE != hDeviceInfo)
    {
        SetupDiDestroyDeviceInfoList(hDeviceInfo);
    }
    if (pDetailData)
    {
        LocalFree(pDetailData);
    }

    return bResult;
}

// WinUsb_howto.docx 記載のプログラムを元に、
// bSync の取り扱いと、異常判定処理を追加。
HANDLE OpenDevice(BOOL bSync)
{
    HANDLE hDev = 0;
    TCHAR devicePath[_MAX_PATH + 1];
    BOOL bResult = TRUE;
    DWORD attrFlags = FILE_ATTRIBUTE_NORMAL;

    bResult = GetDevicePath(
        (LPGUID) &GUID_DEVINTERFACE_WINUSBHIDTESTTARGET,
        devicePath, sizeof(devicePath) / sizeof(devicePath[0]));
    
    if (bResult)
    {
        if (!bSync) 
        {
             // 【筆者註】
             // WinUsb_howto.docx では bSync が無視されていましたが、
             // 使い方を想像してこの分岐処理を追加しました。
            attrFlags |= FILE_FLAG_OVERLAPPED;
        }
        hDev = CreateFile(
            devicePath,
            GENERIC_WRITE | GENERIC_READ,
            FILE_SHARE_WRITE | FILE_SHARE_READ,
            NULL, OPEN_EXISTING, attrFlags, 0);
    }

    return hDev;
}

// -#11-
unsigned int WINAPI IntrThread(LPVOID lpParam)
{
    BOOL bResult = FALSE;
    ULONG i = 0;
    ULONG gotSize = 0;


    while(ThreadLoop){

        bResult = WinUsb_ReadPipe(
                hWinUsb, IntrPipeId, &IntrBuf[0], IntrPipeMaxPktSize, &gotSize, 0);

        assert(bResult);
        if (!bResult) {

            _ftprintf(stderr, TEXT("Interrupt-IN receiving error.\n"));

        } else {

            assert(gotSize <= IntrPipeMaxPktSize);
            for (i = 0; i < gotSize; ++i) {
                _tprintf(TEXT("0x%02x "), IntrBuf[i]);
            }
            _tprintf(TEXT("\n"));
        }
    }

    return 0;
}

void on_exit(void)
{
    if (hThread) {
        // -#13-
        ThreadLoop = FALSE;
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
    }
    WinUsb_Free(hWinUsb);
    CloseHandle(hUsb);
}

// -#10-
/* HID クラスディスクリプタを表示 */
BOOL show_hid_descriptor(WINUSB_INTERFACE_HANDLE  hWinUsb_)
{
    const UCHAR DESCRIPTOR_TYPE_HID    = 0x21u;
    const UCHAR DESCRIPTOR_TYPE_REPORT = 0x22u;

    BOOL bResult = FALSE;

    /*
     * 本当は bLength の値を読み取って適切な大きさのバッファを
     * 確保する方が良いのだが、今回は適度に大きなバッファを
     * 確保するだけで済ませた。
     */
    UCHAR buff[1024];
    ULONG gotSize = 0;

    BYTE bLength = 0;
    BYTE bDescriptorType = 0;
    WORD bcdHID = 0;
    BYTE bCountryCode = 0;
    BYTE bNumDescriptors = 0;
    BYTE bHidDescriptorType = 0;
    WORD wItemLength = 0;
    BYTE currentDesc = 0;

    int i = 0;
    int k = 0;

    /*
     * Get HID Descriptor
     */
    bResult = WinUsb_GetDescriptor(
          hWinUsb_                  /* InterfaceHandle */
        , DESCRIPTOR_TYPE_HID       /* DescriptorType */
        , 0x00u                     /* Index */
        , 0x00u                     /* LanguageID */        
        , &buff[0]                  /* Buffer */            
        , sizeof(buff)              /* BufferLength */      
        , &gotSize                  /* LengthTransferred */ 
        );
    if (!bResult) {
        return FALSE;
    }

    i = 0;

    bLength         =  buff[i++];
    bDescriptorType =  buff[i++];
    bcdHID          =  buff[i++];
    bcdHID          |= (buff[i++] << 8u);
    bCountryCode    =  buff[i++];
    bNumDescriptors =  buff[i++];

    _tprintf(
        TEXT("HID Descriptor (got %d bytes):\n")
        TEXT(" - bLength = 0x%02x\n")
        TEXT(" - bDescriptorType = 0x%02x\n")
        TEXT(" - bcdHID = 0x%04x\n")
        TEXT(" - bCountryCode = 0x%02x\n")
        TEXT(" - bNumDescriptors = 0x%02x\n")
        , gotSize
        , bLength
        , bDescriptorType
        , bcdHID
        , bCountryCode
        , bNumDescriptors
        );

    for (currentDesc = 0; currentDesc < bNumDescriptors; ++currentDesc) {

        bHidDescriptorType =  buff[i++];
        wItemLength        =  buff[i++];
        wItemLength        |= (buff[i++] << 8u);

        _tprintf(
            TEXT(" - HID class descriptor[0x%02x]:\n")
            TEXT("   - bDescriptorType = 0x%02x\n")
            TEXT("   - wItemLength = 0x%04x\n")
            , currentDesc
            , bHidDescriptorType
            , wItemLength
            );
    }
    _tprintf(TEXT("\n"));

    /*
     * Get Report Descriptor
     */
    bResult = WinUsb_GetDescriptor(
          hWinUsb_                  /* InterfaceHandle */
        , DESCRIPTOR_TYPE_REPORT    /* DescriptorType */
        , 0x00u                     /* Index */
        , 0x00u                     /* LanguageID */        
        , &buff[0]                  /* Buffer */            
        , sizeof(buff)              /* BufferLength */      
        , &gotSize                  /* LengthTransferred */ 
        );
    if (!bResult) {
        return FALSE;
    }

    /*
     * 今回は Report Descriptor の内容は解析せず、
     * 単にダンプするだけ。
     */
    _tprintf(TEXT("Report Descriptor (got %d bytes):"), gotSize);

    for (k = 0; k < (int)gotSize; ++k) {
        if (!(k % 8)) {
            _tprintf(TEXT("\n  "));
        }
        _tprintf(TEXT("0x%02x "), buff[k]);
    }
    _tprintf(TEXT("\n\n"));

    return TRUE;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int i = 0;
    BOOL bResult = FALSE;
    USB_INTERFACE_DESCRIPTOR desc;
    WINUSB_PIPE_INFORMATION pipeInfo;


    hUsb = OpenDevice(FALSE);
    if (INVALID_HANDLE_VALUE == hUsb) {
        _ftprintf(stderr, TEXT("OpenDevice() error.\n"));
        exit(-1);
    }
    atexit(on_exit);

    bResult = WinUsb_Initialize(hUsb, &hWinUsb);
    if (!bResult) {
        _ftprintf(stderr, TEXT("WinUsb_Initialize() error.\n"));
        exit(-1);
    }

    /* HID クラスディスクリプタを取得し表示してみます */
    bResult = show_hid_descriptor(hWinUsb);
    if (!bResult) {
        _ftprintf(stderr, TEXT("show_hid_descriptor() error.\n"));
        exit(-1);
    }

    bResult = WinUsb_QueryInterfaceSettings(hWinUsb, 0, &desc);
    if (!bResult)
    {
        _ftprintf(stderr, TEXT("WinUsb_QueryInterfaceSettings() error.\n"));
        exit(-1);
    }

    for (i = 0; i < desc.bNumEndpoints; ++i) {
        bResult = WinUsb_QueryPipe(hWinUsb, 0, (UCHAR)i, &pipeInfo);

        if( !bResult ) {
            break;
        } else if(pipeInfo.PipeType == UsbdPipeTypeInterrupt) {
            /* Interrupt-IN */
            IntrPipeId = pipeInfo.PipeId;
            IntrPipeMaxPktSize = pipeInfo.MaximumPacketSize;
        } else {
            bResult = FALSE;
            break;
        }
    }
    if( !bResult || 0xffu == IntrPipeId ) {
        _ftprintf(stderr, TEXT("Couldn't get Interrupt-IN interface descriptor.\n"));
        exit(-1);
    }
    assert(IntrPipeMaxPktSize <= PIPE_BUFFER_SIZE);

    _ftprintf(stderr
        , TEXT("\n\n=================================================\n")
          TEXT("==== HIT ENTER AND INPUT MOUSE EVENT TO EXIT ====\n")
          TEXT("=================================================\n\n")
        );

    hThread = (HANDLE)_beginthreadex(NULL, 0, IntrThread, 0, 0, &ThreadId);
    if (!hThread) {
        _ftprintf(stderr, TEXT("_beginthreadex() error.\n"));
        exit(-1);
    }

    // -#12-
    /* Wait for a input enter key */
    _gettchar();

    exit(0);
}

/* vim: set expandtab autoindent cindent ts=4 sw=4 number : */
