Source file: /~heha/hsn/hidparse.zip/hidparse/hidparse.cpp

/********************************************************
 * Display HID devices and their usages in a tree	*
 * TODO: Setting some values, showing values live	*
 * haftmann#software, Win32-API				*
 ********************************************************/
#define _WIN32_WINNT 0x0500
#include "hidparse.h"
#include <commdlg.h>
#include <setupapi.h>
#include <devguid.h>
#include <dbt.h>

#define INITGUID
#include <guiddef.h>
#include <usbiodef.h>

#ifdef _DEBUG	// cannot link UUID.lib
DEFINE_GUID(GUID_DEVCLASS_HIDCLASS,0x745a17a0L,0x74d3,0x11d0,0xb6,0xfe,0x00,0xa0,0xc9,0x0f,0x57,0xda);
#endif

HINSTANCE hInst;

UsageDecode ud;

// Decode UsagePage and Usage using HidGen's files (dt.ini, *.upg)
// 0 p u  -> Usage(p:u)
// p p u  -> Usage(u)
// p q u  -> Usage(q:u)
// p 0 u  -> Usage(p:u) not used
static const TCHAR* DecodeUsage(USAGE p, DWORD u) {
 static TCHAR buf[128];
 BYTE flags=1;
 USAGE_AND_PAGE pu=*(USAGE_AND_PAGE*)&u;
 if (!pu.UsagePage) pu.UsagePage=p;
 if (pu.UsagePage!=p) flags=3;
 ud.Decode(pu,flags,buf,elemof(buf));
 return buf;
}

static const TCHAR* DecodeUsagePage(USAGE p) {
 static TCHAR buf[64];
 USAGE_AND_PAGE pu={0,p};
 ud.Decode(pu,2,buf,elemof(buf));
 return buf;
}

// This one tries to generate SI units
static const TCHAR* DecodeUnit(ULONG unit, int&exp) {
 static TCHAR buf[64];
 switch (unit) {
  case 0x11: exp-=2; return T("m");
  case 0x101: exp-=3; return T("kg");
  case 0x1001: return T("s");
  case 0xF011: exp-=2; return T("m/s");
  case 0xF111: exp-=5; return T("mN");
  case 0xE011: exp-=2; return T("m/s²");
  case 0xE111: exp-=5; return T("N");
  case 0xE121: exp-=7; return T("J");
  case 0xE012: return T("rad/s²");
  case 0xF0D121: exp-=7; return T("V");
  case 0x100001: return T("A");
  case 0xE1F1: exp-=1; return T("Pa");
 }
 buf[0]=0;
 return buf;
}

// Another one decodes the unit as-is (TODO!)

static const TCHAR* DecodeCollection(unsigned CollectionType) {
 switch (CollectionType) {
  case 0: return T("Physical");
  case 1: return T("Application");
  case 2: return T("Logical");
  case 3: return T("Report");
  case 4: return T("Named Array");
  case 5: return T("Usage Switch");
  case 6: return T("Usage Modifier");
  default: return T("unknown");
 }
}

// For helping the tree enumeration process
static struct TREE{
 HWND hTree;
 HANDLE hDev;
 PHIDP_PREPARSED_DATA pd;
 HIDP_CAPS hidcaps;
 union{
  struct{
   char *Input[256];		// one (cached) report for each ID possible
   char *Output[256];
   char *Feature[256];
  };
  char *all[3][256];
 }ReportCache;
 HTREEITEM Append(HTREEITEM parent, int iImage, const TCHAR*fmt, ...) const;
 void InsertCollectionNode(HTREEITEM p, const HIDP_LINK_COLLECTION_NODE* n, int i, int par) const;
 HTREEITEM OutCapsComm(HTREEITEM p2, const HIDP_BUTTON_CAPS *caps) const;
 char* GetReport(HIDP_REPORT_TYPE rt, BYTE ReportID, DWORD*ReportLen);
 void FreeReports();
 void OutCaps(HTREEITEM p, const TCHAR*title, HIDP_REPORT_TYPE rt);
 void HexDump(HTREEITEM p, const char*data, int len) const;
 void Build();
}tree;

// Append a textual item and an icon
HTREEITEM TREE::Append(HTREEITEM parent, int iImage, const TCHAR*fmt, ...) const{
 TCHAR s[256];
 wvnsprintf(s,elemof(s),fmt,(va_list)(&fmt+1));
 TVINSERTSTRUCT tvi;
 tvi.hParent=parent;
 tvi.hInsertAfter=TVI_LAST;
 tvi.item.mask=TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
 tvi.item.pszText=s;
 tvi.item.iImage=tvi.item.iSelectedImage=iImage;
 return TreeView_InsertItem(hTree,&tvi);
}

// recursive function to build the tree
void TREE::InsertCollectionNode(HTREEITEM p, const HIDP_LINK_COLLECTION_NODE* n, int i, int par) const{
 p=Append(p,2,T("Collection (%s), %s%s"),
   DecodeCollection(n[i].CollectionType),
   DecodeUsage(par>=0?n[par].LinkUsagePage:0,*(DWORD*)&n[i].LinkUsage),
// possibly short usage for deeper levels but enforce long usage for top-level
   n[i].IsAlias ? T(", ALIAS") : T(""));
 int k=n[i].FirstChild;
 for (int j=0; j<n[i].NumberOfChildren; j++) {
  InsertCollectionNode(p,n,k,i);	// recurse over all children
  k=n[k].NextSibling;		// take next index
 }
}

// HIDP_BUTTON_CAPS and HIDP_VALUE_CAPS is very similar, emit same things here!
HTREEITEM TREE::OutCapsComm(HTREEITEM p2, const HIDP_BUTTON_CAPS *caps) const{
 HTREEITEM p3,p4;
 p3=Append(p2,2,T("%s, ReportID (%d), Link%s%s"),
   DecodeUsagePage(caps->UsagePage),
   caps->ReportID,
   DecodeUsage(caps->UsagePage,*(DWORD*)&caps->LinkUsage),
   caps->IsAlias ? T(", ALIAS") : T(""));
 Append(p3,2,caps->IsRange ? T("UsageMin (0x%02X), UsageMax(0x%02X)") : T("Usage (0x%02X)"),
   caps->Range.UsageMin, caps->Range.UsageMax);
// Do not emit String ID == 0
 if (caps->Range.StringMin) {
  int a=caps->Range.StringMin;
  int e=caps->IsStringRange ? caps->Range.StringMax : a;
  p4=Append(p3,2,caps->IsStringRange ? T("StringMin (0x%02X), StringMax(0x%02X)") : T("StringIndex (0x%02X)"),
    a, e);
// Insert strings here
  WCHAR *str = new WCHAR[128];
  for (;a<=e; a++) {
   HidD_GetIndexedString(hDev,a,str,256);	// What about language ID here??
   Append(p4,1,T("`") USTR T("´"),str);
  }
  delete[] str;
 }
// Do not emit Designator ID == 0
 if (caps->Range.DesignatorMin) {
  int a=caps->Range.DesignatorMin;
  int e=caps->IsDesignatorRange ? caps->Range.DesignatorMax : a;
  p4=Append(p3,2,caps->IsDesignatorRange ? T("DesignatorMin (0x%02X), DesignatorMax(0x%02X)") : T("DesignatorIndex (0x%02X)"),
    a, e);
// TODO: Insert Physical Descriptor here!
  BYTE *buf = new BYTE[1024];
  HidD_GetPhysicalDescriptor(hDev,buf,1024);
  for (;a<=e; a++) {
// TODO: Parse all physical descriptors
  }
  delete[] buf;
 }
 Append(p3,2,caps->IsRange ? T("DataIndexMin (0x%02X), DataIndexMax(0x%02X)") : T("DataIndex (0x%02X)"),
   caps->Range.DataIndexMin, caps->Range.DataIndexMax);
 Append(p3,2,caps->IsAbsolute ? T("ABSOLUTE") : T("RELATIVE"));
 Append(p3,2,T("Bitfield = 0x%02X, LinkCollection = 0x%02X"),
   caps->BitField, caps->LinkCollection);
#if 1	// Not in Win98 DDK! TODO!
 HIDP_EXTENDED_ATTRIBUTES *ea;
 ULONG lea = 1024;
 ea = (HIDP_EXTENDED_ATTRIBUTES*) new BYTE[lea];
 ea->NumGlobalUnknowns=0;
 HidP_GetExtendedAttributes(HidP_Input,caps->NotRange.DataIndex,pd,ea,&lea);
 if (ea->NumGlobalUnknowns) {
  p4=Append(p3,2,T("GlobalUnknowns: %d"),ea->NumGlobalUnknowns);
  PHIDP_UNKNOWN_TOKEN t=ea->GlobalUnknowns;	// is NULL for me!
  if (!t) t=(PHIDP_UNKNOWN_TOKEN)ea->Data;	// seems to contain the data
  for (int i=0; i<ea->NumGlobalUnknowns; i++,t++) {
   Append(p4,2,T("Token = 0x%02X, BitField = 0x%02X = %d"),t->Token,t->BitField,t->BitField);
  }
 }
 delete ea;
#endif
 return p3;
}

void deletePtr(char*&p) {
 delete[] p;
 p=0;
}

static BOOL(WINAPI*pHidD_GetInputReport)(HANDLE,char*,int);

// Retrieves a (cached) (Input/Feature) report, allocate an initalized Output report
char* TREE::GetReport(HIDP_REPORT_TYPE rt, BYTE id, DWORD*ReportLen) {
 DWORD len=(&hidcaps.InputReportByteLength)[rt];
 char **pReport=&ReportCache.all[rt][id];	// pointer to one report buffer array index
 if (len && !*pReport && (*pReport=new char[len])) {
  HidP_InitializeReportForID(rt,id,pd,*pReport,len);
//  **pReport=id;					// Transfer report ID wanted down to system
  switch (rt) {
   case HidP_Input: if (Config.flags&2 && pHidD_GetInputReport) {
    if (!pHidD_GetInputReport(hDev,*pReport,len))// is better suited here but available at XP+
      deletePtr(*pReport);
   }else{
    OVERLAPPED o;
    o.hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
    if (ReadFile(hDev,*pReport,len,&len,&o)	// completes synchronously
    || (GetLastError()==ERROR_IO_PENDING	// OR will complete later
     && !WaitForSingleObject(o.hEvent,50)	// AND completes in a reasonable time frame
     && GetOverlappedResult(hDev,&o,&len,FALSE))){// AND have result: OK
    }else{
     CancelIo(hDev);				// tear down async operation
     deletePtr(*pReport);			// free buffer and clear link
    }
    CloseHandle(o.hEvent);
   }break;

   case HidP_Feature:
   if (!HidD_GetFeature(hDev,*pReport,len))	// OK
     deletePtr(*pReport);			// free buffer and clear link
   break;
  }
 }
 if (ReportLen) *ReportLen=len;
 if (*pReport) **pReport=id;			// (handle erraneous V-USB devices)
 return *pReport;
}

void TREE::FreeReports() {
 for (char** p=&ReportCache.all[0][0]; p<&ReportCache.all[0][0]+3*256/*elemof(ReportCache.all)*/; p++)
   if (*p) deletePtr(*p);
}

// Emit ButtonCaps, ValueCaps, and DataIndices arrays (if any) for one report type
void TREE::OutCaps(HTREEITEM p, const TCHAR*title, HIDP_REPORT_TYPE rt) {
 USHORT *numbers=&hidcaps.NumberInputButtonCaps+3*rt;
 if (!numbers[0] && !numbers[1] && !numbers[2]) return;	// nothing useful!
 p=Append(p,2,title);
 HTREEITEM p2,p3;
 int i;
 char *report;
 ULONG reportlen;
 if (numbers[0]) {
  p2=Append(p,2,T("ButtonCaps"));
  HIDP_BUTTON_CAPS *caps = new HIDP_BUTTON_CAPS[numbers[0]];
  HidP_GetButtonCaps(rt,caps,numbers,pd);
  for (i=0; i<numbers[0]; i++) {
   p3=OutCapsComm(p2,caps+i);
   report=GetReport(rt,caps[i].ReportID,&reportlen);
   if (report) {
    ULONG l;
    l=HidP_MaxUsageListLength(rt,caps[i].UsagePage,pd);
    Append(p3,2,T("MaxUsageListLength: %d"),l);
    USAGE *u = new USAGE[l];
    HidP_GetUsages(rt,caps[i].UsagePage,caps[i].LinkCollection,u,&l,pd,report,reportlen);
    TCHAR s[256],*p=s;
    for (unsigned i=0; i<l; i++) {
     if (i) *p++=',';
     p+=wnsprintf(p,(int)(s+elemof(s)-1-p),T("0x%02X"),u[i]);
    }
    delete[] u;
    Append(p3,3,T("Usages: {%s}"),s);
   }
  }
  delete[] caps;
 }
 if (numbers[1]) {
  p2=Append(p,2,T("ValueCaps"));
  HIDP_VALUE_CAPS *caps = new HIDP_VALUE_CAPS[numbers[1]];
  HidP_GetValueCaps(rt,caps,&numbers[1],pd);
  for (i=0; i<numbers[1]; i++) {
   p3=OutCapsComm(p2,(HIDP_BUTTON_CAPS*)(caps+i));
   int a=caps[i].Range.UsageMin;
   int e=caps[i].IsRange ? caps[i].Range.UsageMax : a;
   Append(p3,2,T("HasNull: %d, BitSize: %d, ReportCount: %d"),
     caps[i].HasNull, caps[i].BitSize, caps[i].ReportCount);
   Append(p3,2,T("LogicalMin: %d, LogicalMax: %d"),
     caps[i].LogicalMin,caps[i].LogicalMax);
   report=GetReport(rt,caps[i].ReportID,&reportlen);
   if (report) {
    ULONG l;
    for(int j=a;j<=e;j++) {
     HidP_GetUsageValue(rt,caps[i].UsagePage,caps[i].LinkCollection,j,&l,pd,report,reportlen);
     Append(p3,3,T("UsageValue: %d {Usage(0x%02X)}"),l,j);
    }
   }
   if (caps[i].UnitsExp || caps[i].Units || caps[i].PhysicalMin || caps[i].PhysicalMax) {
// Windows is floated with bugs here. While exp is a nibble value in the descriptor,
// i.e. -3 = 0x0D (not 0xFD), Windows doesn't sign-expand it to a useful value.
// So I do it myself.
    int exp=(char)(caps[i].UnitsExp<<4)>>4;
    Append(p3,2,T("UnitsExp: %d, Units: 0x%X"),
      exp,caps[i].Units);
    Append(p3,2,T("PhysicalMin: %d, PhysicalMax: %d"),
      caps[i].PhysicalMin,caps[i].PhysicalMax);
    if (report) {
     LONG l;
     for(int j=a;j<=e;j++) {
      HidP_GetScaledUsageValue(rt,caps[i].UsagePage,caps[i].LinkCollection,j,&l,pd,report,reportlen);
      int e=exp;	// e gets trashed
      Append(p3,3,T("ScaledUsageValue: %d * 10^%d %s {Usage(0x%02X)}"),
        l,e,DecodeUnit(caps[i].Units,e),j);
// All Windows versions (Win98, Win2k, Win7-32) expose a bug here:
// While Logical Min/Max AND Physical Min/Max are non-negative,
// an out-of-range error occur in this combination:
// LogicalMin = 0, LogicalMax = 255, Value = 240
     }
    }
   }
  }
  delete[] caps;
 }
 if (numbers[2]) {
  p2=Append(p,3,T("DataIndices"));
  if (report) {
   ULONG len=numbers[2];
   HIDP_DATA *data = new HIDP_DATA[len];
   HidP_GetData(rt,data,&len,pd,report,reportlen);	// Wo ist hier die Report-ID??
   for (i=0; i<(int)len; i++) {
    Append(p2,3,T("DataIndex: 0x%02X, RawValue: %d = 0x%X"),
      data[i].DataIndex,data[i].RawValue,data[i].RawValue);
   }
   delete[] data;
  }
 }
 reportlen=(&hidcaps.InputReportByteLength)[rt];
 p2=Append(p,3,T("Reports read, len = %d (first byte == report ID)"),reportlen);
 for (i=0; i<256; i++) if (ReportCache.all[rt][i]) HexDump(p2,ReportCache.all[rt][i],reportlen);
}

void TREE::HexDump(HTREEITEM p, const char*data, int len) const{
 if (len<=0) return;
 TCHAR s[256], *sp=s;
 do {
  sp+=wsprintf(sp,T("%02X "),(unsigned char)*data++);
  if (--len && sp-s>elemof(s)-8) {
   lstrcpy(sp,T("..."));
   sp+=4;
   break;
  }
 }while(len);
 *--sp=0;		// remove trailing space
 Append(p,3,s);
}

void TREE::Build() {
 GUID hidGuid;		// GUID for HID driver (not the same as GUID_DEVCLASS_HIDCLASS)
 HidD_GetHidGuid(&hidGuid);
 HDEVINFO devs = SetupDiGetClassDevs(&hidGuid, NULL, 0, DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
 if (devs==INVALID_HANDLE_VALUE) return;

 for (int i=0; ; i++) {
  SP_DEVICE_INTERFACE_DATA devinterface;
  devinterface.cbSize = sizeof(devinterface);
  if (!SetupDiEnumDeviceInterfaces(devs, 0, &hidGuid, i, &devinterface)) break;
  SP_DEVINFO_DATA devinfo;
  union{
   SP_DEVICE_INTERFACE_DETAIL_DATA detail;
   TCHAR space[MAX_PATH+4];
  }u;
  devinfo.cbSize = sizeof(devinfo);
  u.detail.cbSize = sizeof(u.detail);
  if (!SetupDiGetDeviceInterfaceDetail(devs, &devinterface, &u.detail, sizeof(u), 0, &devinfo)) {
   Append(TVI_ROOT,0,T("can't get DevicePath for index %d"),i);
   continue;
  }
  hDev = CreateFile(u.detail.DevicePath, GENERIC_READ|GENERIC_WRITE,
    FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
  if (hDev == INVALID_HANDLE_VALUE) 
    hDev = CreateFile(u.detail.DevicePath,0,	// zweiter Versuch für Tastaturen und Mäuse
    FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
  if (hDev == INVALID_HANDLE_VALUE) hDev = 0;

  if (!hDev) {
   Append(TVI_ROOT,0,T("can't open: %s"),u.detail.DevicePath);
   continue;
  }
  if (HidD_GetPreparsedData(hDev,&pd)
  && HidP_GetCaps(pd,&hidcaps)) {
   HTREEITEM p1=Append(TVI_ROOT,0,T("Idx: %d, %s"),i,DecodeUsage(0,*(DWORD*)&hidcaps.Usage));
// Device Strings
   HIDD_ATTRIBUTES a;
   a.Size=sizeof(a);
   HidD_GetAttributes(hDev,&a);
   HTREEITEM p2=Append(p1,1,T("Device Strings and IDs"));
   Append(p2,0,T("DevicePath: `%s´"), u.detail.DevicePath);
   WCHAR str[128];
   if (!HidD_GetManufacturerString(hDev,str,256)) str[0]=0;
   Append(p2,1,T("Manufacturer: `") USTR T("´ (%d = 0x%X)"), str,a.VendorID,a.VendorID);
   if (!HidD_GetProductString(hDev,str,256)) str[0]=0;
   Append(p2,1,T("Product: `") USTR T("´ (%d = 0x%X)"), str,a.ProductID,a.ProductID);
   if (!HidD_GetSerialNumberString(hDev,str,256)) str[0]=0;
   Append(p2,1,T("SerialNumber: `") USTR T("´ (%d = 0x%X)"), str, a.VersionNumber, a.VersionNumber);
// Report lengths
//   p2=Append(p1,2,T("ReportLengths (inclusive ReportID)"));
//   Append(p2,2,T("Input: %d bytes"),hidcaps.InputReportByteLength);
//   Append(p2,2,T("Output: %d bytes"),hidcaps.OutputReportByteLength);
//   Append(p2,2,T("Feature: %d bytes"),hidcaps.FeatureReportByteLength);
// HID-Baum einholen (Baum zeichnen)
   ULONG len = hidcaps.NumberLinkCollectionNodes;
   HIDP_LINK_COLLECTION_NODE* n = new HIDP_LINK_COLLECTION_NODE[len];
   if (HidP_GetLinkCollectionNodes(n,&len,pd)) {
    p2=Append(p1,2,T("LinkColletionNodes"));
    InsertCollectionNode(p2,n,0,-1);
   }
   delete[] n;
// Show information for the Input, Output, and Feature report(s)
   OutCaps(p1,T("Input"),HidP_Input);
   OutCaps(p1,T("Output"),HidP_Output);
   OutCaps(p1,T("Feature"),HidP_Feature);

   HidD_FreePreparsedData(pd); pd=NULL;
  }
  CloseHandle(hDev);
  FreeReports();
 }
 SetupDiDestroyDeviceInfoList(devs);
}

struct CONFIG Config;

TCHAR DtIniPath[MAX_PATH-8];

static void LoadConfig() {
 HKEY k;
 if (!RegOpenKey(HKEY_CURRENT_USER,T("Software\\h#s\\HidParse"),&k)) {
  DWORD len=sizeof(Config);
  RegQueryValueEx(k,T("Config"),NULL,NULL,(LPBYTE)&Config,&len);
  len=sizeof(DtIniPath);
  RegQueryValueEx(k,T("dt.ini"),NULL,NULL,(LPBYTE)DtIniPath,&len);
  RegCloseKey(k);
 }
}

static void SaveConfig() {
 RegSetValue(HKEY_CURRENT_USER,T("Software\\h#s"),REG_SZ,T("haftmann#software"),17*sizeof(TCHAR));
 HKEY k;
 if (!RegCreateKey(HKEY_CURRENT_USER,T("Software\\h#s\\HidParse"),&k)) {
// Store information for human registry hackers
  TCHAR s[64];
  int l=LoadString(0,1,s,elemof(s));
  RegSetValue(k,NULL,REG_SZ,s,l*sizeof(TCHAR));
// Save Window placement info and other things
  RegSetValueEx(k,T("Config"),0,REG_BINARY,(LPBYTE)&Config,sizeof(Config));
// Save path-to-dt.ini
  RegSetValueEx(k,T("dt.ini"),0,REG_SZ,(LPBYTE)DtIniPath,(lstrlen(DtIniPath)+1)*sizeof(TCHAR));
  RegCloseKey(k);
 }
}

static INT_PTR CALLBACK ConfigDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
 switch (Msg) {
  case WM_INITDIALOG: {
   SetDlgItemText(Wnd,10,DtIniPath);
  }return TRUE;

  case WM_COMMAND: switch (LOWORD(wParam)) {
   case 1: GetDlgItemText(Wnd,10,DtIniPath,elemof(DtIniPath)); nobreak;
   case 2: EndDialog(Wnd,wParam); break;

   case 11: {
    OPENFILENAME ofn;
    TCHAR fname[MAX_PATH];
    TCHAR dir[elemof(DtIniPath)];
    TCHAR title[64];
    lstrcpyn(fname,T("dt.ini"),elemof(fname));
    GetDlgItemText(Wnd,10,dir,elemof(dir));
    LoadString(0,11,title,elemof(title));
    ZeroMemory(&ofn,sizeof(ofn));
    *(BYTE*)&ofn.lStructSize=sizeof(ofn);
    ofn.hwndOwner=Wnd;
    ofn.lpstrFilter=T("HID Descriptor Table\0dt.ini\0");
    ofn.lpstrFile=fname;
    ofn.nMaxFile=elemof(fname);
    ofn.lpstrInitialDir=dir;
    ofn.lpstrTitle=title;
    ofn.Flags=OFN_HIDEREADONLY|OFN_FILEMUSTEXIST|OFN_NOCHANGEDIR;
    if (GetOpenFileName(&ofn)) {
     fname[ofn.nFileOffset-1]=0;
     SetDlgItemText(Wnd,10,fname);
    }
   }break;
  }
 }
 return FALSE;
}

static HDEVNOTIFY hNotify;

// The main window is a dialog box with its client area filled with the TreeView.
static INT_PTR CALLBACK MainDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
 switch (Msg) {
  case WM_INITDIALOG: {
   if (Config.showCmd) {
// Reposition window as saved, but ShowWindow() is called later (don't flicker) TODO
    WINDOWPLACEMENT wp;
    wp.length=sizeof(wp);
    GetWindowPlacement(Wnd,&wp);
    Config.winpos>>wp.rcNormalPosition;
    wp.showCmd=Config.showCmd;
    SetWindowPlacement(Wnd,&wp);
    HMENU m=GetMenu(Wnd);
    CheckMenuItem(m,21,Config.flags&1?MF_CHECKED:MF_UNCHECKED);
    CheckMenuItem(m,24,Config.flags&2?MF_CHECKED:MF_UNCHECKED);
    WORD v=(WORD)GetVersion();
    v=v>>8|v<<8;	// swap(), htons() - a smart x86 compiler would generate "xchg ah,al"
    if (v<0x501) EnableMenuItem(m,24,MF_GRAYED);
   }
   DEV_BROADCAST_DEVICEINTERFACE fi;
   fi.dbcc_size=sizeof(fi);
   fi.dbcc_devicetype=DBT_DEVTYP_DEVICEINTERFACE;
//   HidD_GetHidGuid(&fi.dbcc_classguid);
   fi.dbcc_classguid=GUID_DEVINTERFACE_USB_DEVICE;
   hNotify=RegisterDeviceNotification(Wnd,&fi,DEVICE_NOTIFY_WINDOW_HANDLE);
   tree.hTree=GetDlgItem(Wnd,10);
   SP_CLASSIMAGELIST_DATA ild;
   ild.cbSize=sizeof(ild);
   SetupDiGetClassImageList(&ild);
   int iImage;
   SetupDiGetClassImageIndex(&ild,&GUID_DEVCLASS_HIDCLASS,&iImage);
   HICON ico=ImageList_GetIcon(ild.ImageList,iImage,ILD_TRANSPARENT);
   HIMAGELIST il=ImageList_Create(16,16,ILC_MASK,4,0);
   ImageList_AddIcon(il,ico);
   for (int i=65; i<68; i++) {
    ImageList_AddIcon(il,LoadIcon(hInst,MAKEINTRESOURCE(i)));
   }
   TreeView_SetImageList(tree.hTree,il,TVSIL_NORMAL);
   TreeView_SetIndent(tree.hTree,0);	// less waste of space?
   tree.Build();
  }return TRUE;

  case WM_SIZE: {
   tree.hTree=GetDlgItem(Wnd,10);
   SetWindowPos(tree.hTree,0,0,0,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),SWP_NOMOVE|SWP_NOZORDER);
  }break;

  case WM_COMMAND: switch (LOWORD(wParam)) {
   case 2: {
    WINDOWPLACEMENT wp;
    wp.length=sizeof(wp);
    GetWindowPlacement(Wnd,&wp);
    Config.winpos=wp.rcNormalPosition;
    Config.showCmd=(char)wp.showCmd;
    SaveConfig();
    UnregisterDeviceNotification(hNotify);
    EndDialog(Wnd,wParam); 
   }break;
   case 21: {
    Config.flags^=1;
    CheckMenuItem(GetMenu(Wnd),21,Config.flags&1?MF_CHECKED:MF_UNCHECKED);
   }break;
   case 24: {
    Config.flags^=2;
    CheckMenuItem(GetMenu(Wnd),24,Config.flags&2?MF_CHECKED:MF_UNCHECKED);
   }break;
   case 22: {
    TreeView_DeleteAllItems(tree.hTree);
    tree.Build();
   }break;
   case 23: if (DialogBox(0,MAKEINTRESOURCE(23),Wnd,ConfigDlgProc)) SaveConfig();
   break;
   case 99: MessageBox(Wnd,
     T("Parses HID devices using HidD and HidP functions\nhaftmann#software, 04/13"),
     T("About HidParse"),MB_OK|MB_ICONINFORMATION); break;
  }break;

  case WM_DEVICECHANGE: switch (wParam) {
   case DBT_DEVICEARRIVAL:
   case DBT_DEVICEREMOVECOMPLETE: {
    if (Config.flags&1) SendMessage(Wnd,WM_COMMAND,22,0);
   }break;
  }break;

 }
 return FALSE;
}

/********************
 * main entry point *
 ********************/
EXTERN_C void CALLBACK WinMainCRTStartup() {
 hInst=GetModuleHandle(NULL);
 InitCommonControls();
 Config.flags=0x01;
 HINSTANCE h=LoadLibraryA("HID");
 if (h) {
  pHidD_GetInputReport=(BOOL(WINAPI*)(HANDLE,char*,int))GetProcAddress(h,"HidD_GetInputReport");
  FreeLibrary(h);
 }
 lstrcpyn(DtIniPath,T("C:\\Programme\\MSVC\\DDK\\tools\\HidGen"),elemof(DtIniPath));
 LoadConfig();
 ud.Init(DtIniPath);
 ExitProcess((UINT)DialogBox(0,MAKEINTRESOURCE(100),0,MainDlgProc));
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded