/* * DEVICES.C * * Point-of-Sale Control Panel Applet * * Author: Ervin Peretz * * (c) 2001 Microsoft Corporation */ #include #include #include #include #include #include #include "internal.h" #include "res.h" #include "debug.h" ULONG numDeviceInstances = 0; LIST_ENTRY allPOSDevicesList; posDevice *NewPOSDevice( DWORD dialogId, HANDLE devHandle, PWCHAR devPath, PHIDP_PREPARSED_DATA pHidPreparsedData, PHIDD_ATTRIBUTES pHidAttrib, HIDP_CAPS *pHidCapabilities) { posDevice *newPosDev; newPosDev = (posDevice *)GlobalAlloc( GMEM_FIXED|GMEM_ZEROINIT, sizeof(posDevice)); if (newPosDev){ newPosDev->sig = POSCPL_SIG; InitializeListHead(&newPosDev->listEntry); newPosDev->dialogId = dialogId; newPosDev->devHandle = devHandle; WStrNCpy(newPosDev->pathName, devPath, MAX_PATH); newPosDev->hidPreparsedData = pHidPreparsedData; newPosDev->hidAttrib = *pHidAttrib; newPosDev->hidCapabilities = *pHidCapabilities; /* * Allocate components of the context */ if (newPosDev->hidCapabilities.InputReportByteLength){ newPosDev->readBuffer = GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, newPosDev->hidCapabilities.InputReportByteLength); } if (newPosDev->hidCapabilities.OutputReportByteLength){ newPosDev->writeBuffer = GlobalAlloc( GMEM_FIXED|GMEM_ZEROINIT, newPosDev->hidCapabilities.OutputReportByteLength); } #if USE_OVERLAPPED_IO newPosDev->overlappedReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL); newPosDev->overlappedWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL); #endif /* * Check if we allocated everything successfully. */ if ( (newPosDev->readBuffer || !newPosDev->hidCapabilities.InputReportByteLength) && (newPosDev->writeBuffer || !newPosDev->hidCapabilities.OutputReportByteLength) && #if USE_OVERLAPPED_IO newPosDev->overlappedReadEvent && newPosDev->overlappedWriteEvent && #endif TRUE ){ /* * Created device context successfully. */ } else { DBGERR(L"allocation error in NewPOSDevice()", 0); DestroyPOSDevice(newPosDev); newPosDev = NULL; } } ASSERT(newPosDev); return newPosDev; } VOID DestroyPOSDevice(posDevice *posDev) { ASSERT(IsListEmpty(&posDev->listEntry)); /* * Note: this destroy function is called from a failed NewPOSDevice() * call as well; so check every pointer before freeing. */ if (posDev->readBuffer) GlobalFree(posDev->readBuffer); if (posDev->writeBuffer) GlobalFree(posDev->writeBuffer); if (posDev->hidPreparsedData) GlobalFree(posDev->hidPreparsedData); #if USE_OVERLAPPED_IO if (posDev->overlappedReadEvent) CloseHandle(posDev->overlappedReadEvent); if (posDev->overlappedWriteEvent) CloseHandle(posDev->overlappedWriteEvent); #endif GlobalFree(posDev); } VOID EnqueuePOSDevice(posDevice *posDev) { ASSERT(IsListEmpty(&posDev->listEntry)); InsertTailList(&allPOSDevicesList, &posDev->listEntry); numDeviceInstances++; } VOID DequeuePOSDevice(posDevice *posDev) { ASSERT(!IsListEmpty(&allPOSDevicesList)); ASSERT(!IsListEmpty(&posDev->listEntry)); RemoveEntryList(&posDev->listEntry); InitializeListHead(&posDev->listEntry); numDeviceInstances--; } posDevice *GetDeviceByHDlg(HWND hDlg) { posDevice *foundPosDev = NULL; LIST_ENTRY *listEntry; listEntry = &allPOSDevicesList; while ((listEntry = listEntry->Flink) != &allPOSDevicesList){ posDevice *thisPosDev; thisPosDev = CONTAINING_RECORD(listEntry, posDevice, listEntry); if (thisPosDev->hDlg == hDlg){ foundPosDev = thisPosDev; break; } } return foundPosDev; } VOID OpenAllHIDPOSDevices() { HDEVINFO hDevInfo; GUID hidGuid = {0}; WCHAR devicePath[MAX_PATH]; /* * Call hid.dll to get the GUID for Human Input Devices. */ HidD_GetHidGuid(&hidGuid); hDevInfo = SetupDiGetClassDevs( &hidGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hDevInfo == INVALID_HANDLE_VALUE){ DWORD err = GetLastError(); DBGERR(L"SetupDiGetClassDevs failed", err); } else { int i; for (i = 0; TRUE; i++){ SP_DEVICE_INTERFACE_DATA devInfoData = {0}; BOOL ok; devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); ok = SetupDiEnumDeviceInterfaces( hDevInfo, 0, &hidGuid, i, &devInfoData); if (ok){ DWORD hwDetailLen = 0; /* * Call SetupDiGetDeviceInterfaceDetail with * a NULL PSP_DEVICE_INTERFACE_DETAIL_DATA pointer * just to get the length of the hardware details. */ ASSERT(devInfoData.cbSize == sizeof(SP_DEVICE_INTERFACE_DATA)); ok = SetupDiGetDeviceInterfaceDetail( hDevInfo, &devInfoData, NULL, 0, &hwDetailLen, NULL); if (ok || (GetLastError() == ERROR_INSUFFICIENT_BUFFER)){ PSP_DEVICE_INTERFACE_DETAIL_DATA devDetails; /* * Now make the real call to SetupDiGetDeviceInterfaceDetail. */ ASSERT(hwDetailLen > sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA)); devDetails = GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, hwDetailLen); if (devDetails){ devDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); ok = SetupDiGetDeviceInterfaceDetail( hDevInfo, &devInfoData, devDetails, hwDetailLen, &hwDetailLen, NULL); if (ok){ /* * BUGBUG - FINISH * Right now, we only handle cash drawers. * And we only work with the APG cash drawers * (with vendor-specific usages) for now. */ // APG Cash Drawer wrote to say their VID is actually 1989 // WCHAR apgKbPathPrefix[] = L"\\\\?\\hid#vid_0f25&pid_0500"; // Correct APG Cash Drawer VID (1989) to hexcode (07C5) // WCHAR apgKbPathPrefix[] = L"\\\\?\\hid#vid_1989&pid_0500"; WCHAR apgKbPathPrefix[] = L"\\\\?\\hid#vid_07c5&pid_0500"; /* * If this is an APG keyboard, then the device path * (very long) will begin with apgKbPathPrefix. */ if (RtlEqualMemory( devDetails->DevicePath, apgKbPathPrefix, sizeof(apgKbPathPrefix)-sizeof(WCHAR))){ HANDLE hDev; // MessageBox(NULL, devDetails->DevicePath, L"DEBUG message - found APG kb", MB_OK); hDev = CreateFile( devDetails->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hDev == INVALID_HANDLE_VALUE){ DWORD err = GetLastError(); DBGERR(L"CreateFile failed", err); } else { PHIDP_PREPARSED_DATA hidPreparsedData; // MessageBox(NULL, devDetails->DevicePath, L"DEBUG message - CreateFile succeeded", MB_OK); ok = HidD_GetPreparsedData(hDev, &hidPreparsedData); if (ok){ HIDD_ATTRIBUTES hidAttrib; ok = HidD_GetAttributes(hDev, &hidAttrib); if (ok){ HIDP_CAPS hidCapabilities; ok = HidP_GetCaps(hidPreparsedData, &hidCapabilities); if (ok){ posDevice *posDev; posDev = NewPOSDevice( IDD_POS_CASHDRAWER_DLG, hDev, devDetails->DevicePath, hidPreparsedData, &hidAttrib, &hidCapabilities); if (posDev){ EnqueuePOSDevice(posDev); } else { ASSERT(posDev); } } else { DBGERR(L"HidP_GetCaps failed", 0); } } else { DWORD err = GetLastError(); DBGERR(L"HidD_GetAttributes failed", err); } } else { DWORD err = GetLastError(); DBGERR(L"HidD_GetPreparsedData failed", err); } } } } else { DWORD err = GetLastError(); DBGERR(L"SetupDiGetDeviceInterfaceDetail(2) failed", err); } GlobalFree(devDetails); } } else { DWORD err = GetLastError(); DBGERR(L"SetupDiGetDeviceInterfaceDetail(1) failed", err); } } else { DWORD err = GetLastError(); if (err != ERROR_NO_MORE_ITEMS){ DBGERR(L"SetupDiEnumDeviceInterfaces failed", err); } break; } } SetupDiDestroyDeviceInfoList(hDevInfo); } } /* * LaunchDeviceInstanceThread * * Launch a thread for a device instance to read * asynchronous events from the device. */ VOID LaunchDeviceInstanceThread(posDevice *posDev) { DWORD threadId; posDev->hThread = CreateThread(NULL, 0, DeviceInstanceThread, posDev, 0, &threadId); if (posDev->hThread){ } else { DWORD err = GetLastError(); DBGERR(L"CreateThread failed", err); } } #if USE_OVERLAPPED_IO VOID CALLBACK OverlappedReadCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { posDevice *posDev; /* * We stashed our context in the hEvent field of the * overlapped structure (this is allowed). */ ASSERT(lpOverlapped); posDev = lpOverlapped->hEvent; ASSERT(posDev->sig == POSCPL_SIG); posDev->overlappedReadStatus = dwErrorCode; posDev->overlappedReadLen = dwNumberOfBytesTransfered; SetEvent(posDev->overlappedReadEvent); } #endif DWORD __stdcall DeviceInstanceThread(void *context) { posDevice *posDev = (posDevice *)context; HANDLE hDevNew; ASSERT(posDev->sig == POSCPL_SIG); // BUGBUG - for some reason, reads and writes on the same handle // interfere with one another hDevNew = CreateFile( posDev->pathName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hDevNew == INVALID_HANDLE_VALUE){ DWORD err = GetLastError(); DBGERR(L"CreateFile failed", err); } else { /* * Loop forever until main thread kills this thread. */ while (TRUE){ WCHAR drawerStateString[100]; DWORD bytesRead = 0; BOOL ok; /* * Load the default string for the drawer state */ LoadString(g_hInst, IDS_DRAWERSTATE_UNKNOWN, drawerStateString, 100); ASSERT(posDev->hidCapabilities.InputReportByteLength > 0); ASSERT(posDev->readBuffer); #if USE_OVERLAPPED_IO /* * It's ok to stash our context in the hEvent field * of the overlapped structure. */ posDev->overlappedReadInfo.hEvent = (HANDLE)posDev; posDev->overlappedReadInfo.Offset = 0; posDev->overlappedReadInfo.OffsetHigh = 0; posDev->overlappedReadLen = 0; ResetEvent(posDev->overlappedReadEvent); ok = ReadFileEx(hDevNew, posDev->readBuffer, posDev->hidCapabilities.InputReportByteLength, &posDev->overlappedReadInfo, OverlappedReadCompletionRoutine); if (ok){ WaitForSingleObject(posDev->overlappedReadEvent, INFINITE); ok = (posDev->overlappedReadStatus == NO_ERROR); bytesRead = posDev->overlappedWriteLen; } else { bytesRead = 0; } #else ok = ReadFile( hDevNew, posDev->readBuffer, posDev->hidCapabilities.InputReportByteLength, &bytesRead, NULL); #endif if (ok){ NTSTATUS ntStat; ULONG usageVal; ASSERT(bytesRead <= posDev->hidCapabilities.InputReportByteLength); ntStat = HidP_GetUsageValue(HidP_Input, USAGE_PAGE_CASH_DEVICE, 0, // all collections USAGE_CASH_DRAWER_STATUS, &usageVal, posDev->hidPreparsedData, posDev->readBuffer, posDev->hidCapabilities.InputReportByteLength); if (ntStat == HIDP_STATUS_SUCCESS){ HWND hOpenButton; /* * Get display string for new drawer state. */ switch (usageVal){ case DRAWER_STATE_OPEN: LoadString(g_hInst, IDS_DRAWERSTATE_OPEN, drawerStateString, 100); break; case DRAWER_STATE_CLOSED_READY: LoadString(g_hInst, IDS_DRAWERSTATE_READY, drawerStateString, 100); break; case DRAWER_STATE_CLOSED_CHARGING: LoadString(g_hInst, IDS_DRAWERSTATE_CHARGING, drawerStateString, 100); break; case DRAWER_STATE_LOCKED: LoadString(g_hInst, IDS_DRAWERSTATE_LOCKED, drawerStateString, 100); break; default: DBGERR(L"illegal usage", usageVal); break; } /* * Set 'Open' button based on the drawer state. */ hOpenButton = GetDlgItem(posDev->hDlg, IDC_CASHDRAWER_OPEN); if (hOpenButton){ LONG btnState = GetWindowLong(hOpenButton, GWL_STYLE); switch (usageVal){ case DRAWER_STATE_OPEN: btnState |= WS_DISABLED; break; default: btnState &= ~WS_DISABLED; break; } SetWindowLong(hOpenButton, GWL_STYLE, btnState); /* * To make SetWindowLong take effect, you * sometimes have to call SetWindowPos. */ SetWindowPos(hOpenButton, 0, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_HIDEWINDOW); SetWindowPos(hOpenButton, 0, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_SHOWWINDOW); } else { DBGERR(L"GetDlgItem failed", 0); } } else { DBGERR(L"HidP_GetUsageValue failed", ntStat); DBGERR(DBGHIDSTATUSSTR(ntStat), 0); } } else { DWORD err = GetLastError(); DBGERR(L"ReadFile failed", err); } ASSERT(posDev->hDlg); ok = SetDlgItemText(posDev->hDlg, IDC_CASHDRAWER_STATETEXT, drawerStateString); if (ok){ } else { DWORD err = GetLastError(); DBGERR(L"SetDlgItemText failed", err); } } CloseHandle(hDevNew); } return NO_ERROR; }