// winusbtest.cpp : アプリケーション用のエントリ ポイントの定義
//

#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <assert.h>
#include <winusb.h>
#include <setupapi.h>
#include "resource.h"

#include <initguid.h>
// Interface GUID for PID=0x9999 & VID=0x1232 target
// {63275336-530B-4069-92B6-5F8AE3465462}
DEFINE_GUID(GUID_DEVINTERFACE_WINUSBTESTTARGET, 
  0x63275336, 0x530b, 0x4069, 0x92, 0xb6, 0x5f, 0x8a, 0xe3, 0x46, 0x54, 0x62);

#define PIPE_BUFFER_SIZE 128

BOOL ThreadLoop = 1;
HWND hMainWnd;
HANDLE hThread = NULL;
unsigned int ThreadId; 
HANDLE hUsb = INVALID_HANDLE_VALUE;
WINUSB_INTERFACE_HANDLE hWinUsb = 0;
UCHAR LedPipeId = 0xffu;        // Bulk-OUT pipe ID
UCHAR SwPipeId = 0xffu;         // Bulk-IN pipe ID
UCHAR IntrPipeId = 0xffu;       // Interrupt-IN pipe ID
USHORT LedPipeMaxPktSize = 0u;  // Bulk-OUT maximum packet size
USHORT SwPipeMaxPktSize = 0u;   // Bulk-IN maximum packet size
USHORT IntrPipeMaxPktSize = 0u; // Interrupt-IN maximum packet size
TCHAR szClassNme[] = TEXT("WINUSBTEST");
unsigned char LedBuf[PIPE_BUFFER_SIZE] = { 0 };
unsigned char SwBuf[PIPE_BUFFER_SIZE] = { 0 };
unsigned char IntrBuf[PIPE_BUFFER_SIZE] = { 0 };

// -#1-
// 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;
}

// -#2-
// 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_WINUSBTESTTARGET,
        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;
}

LRESULT CALLBACK DlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    BOOL bResult = FALSE;
    ULONG size;
    
    switch( uMsg ){
    case WM_INITDIALOG:
		SetTimer(hWnd, 0x1111, 100, NULL);
        return FALSE;
    case WM_TIMER:
        // -#6-
        bResult = WinUsb_ReadPipe(
                hWinUsb, SwPipeId, &SwBuf[0], SwPipeMaxPktSize, &size, 0);
        if(!bResult) {
            KillTimer(hWnd, 0x1111);
            MessageBox(hWnd,TEXT("バルクインパイプの読み取りに失敗しました"),TEXT("winusbtest"),MB_OK | MB_ICONSTOP);
            DestroyWindow( hWnd );
            break;
        }

        if(SwBuf[0] & 0x01) {
            SendDlgItemMessage(hWnd, IDC_CHECK5 , BM_SETCHECK , BST_CHECKED, 0);
        } else {
            SendDlgItemMessage(hWnd, IDC_CHECK5 , BM_SETCHECK , BST_UNCHECKED, 0);
        }

        if(SwBuf[0] & 0x02) {
            SendDlgItemMessage(hWnd, IDC_CHECK6 , BM_SETCHECK , BST_CHECKED, 0);
        } else {
            SendDlgItemMessage(hWnd, IDC_CHECK6 , BM_SETCHECK , BST_UNCHECKED, 0);
        }

        if(SwBuf[0] & 0x04) {
            SendDlgItemMessage(hWnd, IDC_CHECK7 , BM_SETCHECK , BST_CHECKED, 0);
        } else {
            SendDlgItemMessage(hWnd, IDC_CHECK7 , BM_SETCHECK , BST_UNCHECKED, 0);
        }

        if(SwBuf[0] & 0x08) {
            SendDlgItemMessage(hWnd, IDC_CHECK8 , BM_SETCHECK , BST_CHECKED, 0);
        } else {
            SendDlgItemMessage(hWnd, IDC_CHECK8 , BM_SETCHECK , BST_UNCHECKED, 0);
        }

        break;
    case WM_COMMAND:
        switch( LOWORD( wParam ) ){
        case IDC_BUTTON1:
            LedBuf[0] = 0; 
            
            if(SendDlgItemMessage(hWnd, IDC_CHECK1, BM_GETCHECK, 0 , 0)) {
                LedBuf[0] |= 0x01;
            } 
            
            if(SendDlgItemMessage(hWnd, IDC_CHECK2, BM_GETCHECK, 0 , 0)) {
                LedBuf[0] |= 0x02;
            } 

            if(SendDlgItemMessage(hWnd, IDC_CHECK3, BM_GETCHECK, 0 , 0)) {
                LedBuf[0] |= 0x04;
            }

            if(SendDlgItemMessage(hWnd, IDC_CHECK4, BM_GETCHECK, 0 , 0)) {
                LedBuf[0] |= 0x08;
            }

        // -#7-
        bResult = WinUsb_WritePipe(
                hWinUsb, LedPipeId, &LedBuf[0], LedPipeMaxPktSize, &size, 0);
        if(!bResult) {
                KillTimer(hWnd, 0x1111);
                MessageBox(hWnd,TEXT("バルクアウトパイプの書き込みに失敗しました"),TEXT("winusbtest"),MB_OK | MB_ICONSTOP);
                DestroyWindow( hWnd );
            }
            break;
        default:
            DefWindowProc( hWnd, uMsg, wParam, lParam );
        }
        break;
    case WM_CLOSE:
        ThreadLoop = 0;
        KillTimer(hWnd, 0x1111);
        DestroyWindow( hWnd );
        break;
    case WM_DESTROY:
        PostQuitMessage( 0 );
        break;
    default:
        return DefWindowProc( hWnd, uMsg, wParam, lParam );
    }

    return TRUE;
}

// -#8-
unsigned int WINAPI IntrThread(LPVOID lpParam)
{
    BOOL bResult;
    int st;
    ULONG size;

    st = 0;

    while(ThreadLoop){
        Sleep(50);

        bResult = WinUsb_ReadPipe(
                hWinUsb, IntrPipeId, &IntrBuf[0], IntrPipeMaxPktSize, &size, 0);
        if(!bResult)
        {
            return 0;
        }

        if((IntrBuf[0] & 0x01) && (st == 0)) {
            st = 1;
            SendDlgItemMessage(hMainWnd, IDC_CHECK9, BM_SETCHECK , BST_CHECKED, 0);
            SetDlgItemText(hMainWnd, IDC_CHECK9, TEXT("ボタンが押されています"));
        } else if((IntrBuf[0] == 0x00) && (st == 1)){
            st = 0;
            SendDlgItemMessage(hMainWnd, IDC_CHECK9 , BM_SETCHECK , BST_UNCHECKED, 0);
            SetDlgItemText(hMainWnd, IDC_CHECK9, TEXT("ボタンは押されていません"));
        }
    }

    return 0;
}

BOOL InitApp( HINSTANCE hInst )
{
    WNDCLASS wc;

    wc.style            = 0;
    wc.lpfnWndProc      = DlgProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = DLGWINDOWEXTRA;
    wc.hInstance        = hInst;
    wc.hIcon            = LoadIcon( NULL, IDI_APPLICATION );
    wc.hCursor          = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground    = (HBRUSH)( COLOR_BTNFACE + 1 );
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = szClassNme;

    return RegisterClass( &wc );
}

BOOL InitInstance( HINSTANCE hInst, int nCmdShow )
{
    hMainWnd = CreateDialog(hInst, MAKEINTRESOURCE( IDD_DIALOG_MAIN ),
                                                        0, (DLGPROC)DlgProc );
    if( hMainWnd == NULL ){
        DWORD err = GetLastError();
        return FALSE;
    }

    ShowWindow( hMainWnd, nCmdShow );
    UpdateWindow( hMainWnd );

    return TRUE;
}

int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPreInst,
                                    LPTSTR lpszCmdLine, int nCmdShow )
{
    MSG msg;
    int i = 0;
    BOOL bResult = FALSE;
    USB_INTERFACE_DESCRIPTOR desc;
    WINUSB_PIPE_INFORMATION pipeInfo;

    if( !InitApp( hInstance ) ){
        return FALSE;
    }

    hUsb = OpenDevice(FALSE);
    if (INVALID_HANDLE_VALUE == hUsb)
    {
        MessageBox(0,TEXT("デバイスを開けませんでした"),TEXT("winusbtest"),MB_OK | MB_ICONSTOP);
        return FALSE;
    }

    // -#3-
    bResult = WinUsb_Initialize(hUsb, &hWinUsb);
    if (!bResult)
    {
        MessageBox(0,TEXT("WinUSB 初期化に失敗しました"),TEXT("winusbtest"),MB_OK | MB_ICONSTOP);
        CloseHandle(hUsb);
        return FALSE;
    }

    // -#4-
    bResult = WinUsb_QueryInterfaceSettings(hWinUsb, 0, &desc);
    if (!bResult)
    {
        MessageBox(0,TEXT("インターフェースディスクリプタ取得に失敗しました"),TEXT("winusbtest"),MB_OK | MB_ICONSTOP);
        WinUsb_Free(hWinUsb);
        CloseHandle(hUsb);
        return FALSE;
    }

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

        if( !bResult )
        {
            break;
        }
        else if(pipeInfo.PipeType == UsbdPipeTypeBulk &&
                  USB_ENDPOINT_DIRECTION_OUT(pipeInfo.PipeId))
        {
            /* Bulk-OUT */
            LedPipeId = pipeInfo.PipeId;
            LedPipeMaxPktSize = pipeInfo.MaximumPacketSize;
        }
        else if(pipeInfo.PipeType == UsbdPipeTypeBulk &&
                  USB_ENDPOINT_DIRECTION_IN(pipeInfo.PipeId))
        {
            /* Bulk-IN */
            SwPipeId = pipeInfo.PipeId;
            SwPipeMaxPktSize = pipeInfo.MaximumPacketSize;
        }
        else if(pipeInfo.PipeType == UsbdPipeTypeInterrupt)
        {
            /* Interrupt-IN */
            IntrPipeId = pipeInfo.PipeId;
            IntrPipeMaxPktSize = pipeInfo.MaximumPacketSize;
        }
        else
        {
            bResult = FALSE;
            break;
        }
    }
    if( !bResult ||
        0xffu == LedPipeId || 0xffu == SwPipeId || 0xffu == IntrPipeId )
    {
        MessageBox(0,TEXT("インターフェースディスクリプタ解析に失敗しました"),TEXT("winusbtest"),MB_OK | MB_ICONSTOP);
        WinUsb_Free(hWinUsb);
        CloseHandle(hUsb);
        return FALSE;
    }
    assert(LedPipeMaxPktSize <= PIPE_BUFFER_SIZE);
    assert(SwPipeMaxPktSize <= PIPE_BUFFER_SIZE);
    assert(IntrPipeMaxPktSize <= PIPE_BUFFER_SIZE);

    if( !InitInstance( hInstance, nCmdShow ) ){
        WinUsb_Free(hWinUsb);
        CloseHandle(hUsb);
        return FALSE;
    }

    hThread = (HANDLE)_beginthreadex(NULL, 0, IntrThread, 0, 0, &ThreadId);

    while( GetMessage( &msg, NULL, 0, 0 ) ){
        if( IsDialogMessage( hMainWnd, &msg ) == FALSE ){
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
    }

    if(hThread)
    {
        ThreadLoop = 0;
        WaitForSingleObject(hThread, 1000);
        CloseHandle(hThread);
    }

    // -#9-
    WinUsb_Free(hWinUsb);
    CloseHandle(hUsb);

    return msg.wParam;
}

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