You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
548 lines
21 KiB
548 lines
21 KiB
/*
|
|
* DEVICES.C
|
|
*
|
|
* Point-of-Sale Control Panel Applet
|
|
*
|
|
* Author: Ervin Peretz
|
|
*
|
|
* (c) 2001 Microsoft Corporation
|
|
*/
|
|
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
#include <commctrl.h>
|
|
#include <cpl.h>
|
|
|
|
#include <setupapi.h>
|
|
#include <hidsdi.h>
|
|
|
|
#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;
|
|
}
|
|
|