{*******************************************************}
{                                                       }
{       nrCommLib Communication Library for Delphi      }
{       TnrHid component                                }
{       Copyright (c) 1997-2011 Roman Novgorodov        }
{       v9.27                                           }
{       last update: 21 Oct 2008                        }
{                                                       }
{       e-mail:noro@deepsoftware.com                    }
{       DeepSoftware                                    }
{       http://www.deepsoftware.com/                    }
{                                                       }
{*******************************************************}
{$I nrcommlib.inc}
unit nrhid;

interface
uses nrclasses, classes, windows , nrhidw32;

Type

  TnrHidDevice = class(TnrDevice)
  private
    fSerialNumber: string;
    fPhysicalDescriptor: string;
    function GetProductId: word;
    function GetVendorId: word;
    function GetVersion: word;
    function GetButtons(Index: Integer; i: integer): HIDP_BUTTON_CAPS;
    function GetValues(Index: Integer; i: integer): HIDP_VALUE_CAPS;
    function GetCount(Index: Integer): word;
    function GetUsage: word;
    function GetUsagePage: word;
    function GetReportLength(const Index: Integer): dword;
  protected
    fPpd:THandle;
    fAttributes:HIDD_ATTRIBUTES;
    fCaps:HIDP_CAPS;
    fCapsInputButtons: pointer;
    fCapsInputValues: pointer;
    fCapsOutputButtons: pointer;
    fCapsOutputValues: pointer;
    fCapsFeatureButtons: pointer;
    fCapsFeatureValues: pointer;

  public
    constructor Create(aBase:TnrDevice);
    destructor Destroy; override;
//    property SerialNumber:string read fSerialNumber;
    property PhysicalDescriptor:string read fPhysicalDescriptor;
    property VendorId:word read GetVendorId;
    property ProductId:word read GetProductId;
    property Version:word read GetVersion;
    property Usage:word read GetUsage;
    property UsagePage:word read GetUsagePage;    

    property InputButtons[i:integer]:HIDP_BUTTON_CAPS  index 0 read GetButtons;
    property InputValues[i:integer]:HIDP_VALUE_CAPS  index 1 read GetValues;
    property OutputButtons[i:integer]:HIDP_BUTTON_CAPS  index 3 read GetButtons;
    property OutputValues[i:integer]:HIDP_VALUE_CAPS  index 4 read GetValues;
    property FeatureButtons[i:integer]:HIDP_BUTTON_CAPS  index 6 read GetButtons;
    property FeatureValues[i:integer]:HIDP_VALUE_CAPS  index 7 read GetValues;

    property InputButtonsCount:word index 0 read GetCount;
    property InputValuesCount:word  index 1 read GetCount;
    property OutputButtonsCount:word index 3 read GetCount;
    property OutputValuesCount:word index 4 read   GetCount;
    property FeatureButtonsCount:word index 6 read  GetCount;
    property FeatureValuesCount:word index 7 read  GetCount;

    property OutputReportLength:dword index 0 read GetReportLength;
    property InputReportLength:dword index 1 read GetReportLength;
    property FeatureReportLength:dword index 2 read GetReportLength;

  end;

  TnrHid = class(TnrBaseIO)
  private
    fDeviceIndex:integer;
    fHidDevice: TnrHidDevice;
    fOutputReport:dword;
    fInputReport:dword;
    fFeatureReport:dword;
    fLenOutputReport:dword;
    fLenInputReport:dword;
    fLenFeatureReport:dword;
    fIsWriteMode: boolean;
    fIsReadMode: boolean;
    fSharedMode : boolean;
    hEventThread :THandle;
    flLock:boolean;
    fEnumFullMode:boolean;
    function OpenHandle(aName:string; const flRead, flWrite, flShared, flOvl:boolean):THandle;
    function LoadDeviceCapabilities(dev:TnrHidDevice; fullMode:boolean):boolean;
    procedure AllocReportBuffer;
    procedure FreeReportBuffer;
  protected
    procedure DeviceListLoad; override;
    function GetDeviceIndex: integer; override;
    procedure SetDeviceIndex(const Value: integer);  override;
    function RequestDeviceHandle:THandle; override;
    procedure RequestDeviceHandleClose(aHandle:THandle); override;
    procedure ThreadExecute(aThread:TThread); override;
    procedure Close; override;
    function DoInputData(Data : PAnsiChar; Len:cardinal):boolean; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Update; override;
    procedure UpdateDeviceDetails; virtual;

    procedure SendData(const Buff:PAnsiChar; const N:cardinal); override;
    procedure SendChar(const Ch:Char);    override;
    procedure SendString(const S:AnsiString); override;
    procedure ClearDeviceInput; override;
    procedure ClearDeviceOutput; override;
    procedure ClearDeviceBuffers; override;

    function ReadReport(const pcReport:pointer; const ReportSize:cardinal):boolean;
    function WriteReport(const pcReport: pointer; const ReportSize:cardinal):boolean;

    function GetInputReport(const pcReport:pointer; const ReportSize:cardinal):boolean;
    function SetOutputReport(const pcReport: pointer; const ReportSize:cardinal):boolean;

    function ReadFeature(const pcReport: pointer; const ReportSize: cardinal): boolean;
    function WriteFeature(const pcReport: pointer; const ReportSize: cardinal): boolean;

    procedure Reset;
    procedure Remove;
    procedure Enable;
    procedure Disable;
    procedure Restart;

    property HidDevice:TnrHidDevice read fHidDevice;
    //property Active;
    property IsReadMode:boolean read fIsReadMode;
    property IsWriteMode:boolean read fIsWriteMode;

    property OutputReportLength:dword read fLenOutputReport;
    property InputReportLength:dword read fLenInputReport;
    property FeatureReportLength:dword read fLenFeatureReport;
    property Handle;

{    property OutputReport read fLenOutputReport;
    property InputReportLength:dword read fLenInputReport;
    property FeatureReportLength:dword read fLenFeatureReport;}

  published
    property SharedMode:boolean read fSharedMode write fSharedMode;
    property EnumFullMode:boolean read fEnumFullMode write fEnumFullMode;
    property DataProcessor;
    property Active;
    property Terminal;
    property TerminalUsage;
    property TerminalEcho;
    property DeviceIndex;
    property Log;
    property Priority;
    property UseMainThread;
    property OnOpen;
    property OnClose;
    property OnAfterReceive;
    property OnFatalError;
  end;

  function GetUsagePageString(aUsage:word):string;

implementation
uses nrwin32di, SysUtils,
    {$IFDEF TRIAL}
    shellapi,
    {$ENDIF}
    nrwin32;

Var
  GUID_HID_CLASS : TGUID;

  {$IFDEF TRIAL}
  MsgText:{$IFNDEF LINUX}PCHar{$ELSE}WideString{$ENDIF}=
    'nrComm Lib '+nrCommLibVersion+' Copyright(C) 1997-2011 DeepSoftware'+Eoln+
    Eoln+'You are using the trial version ! Please register !!!'+
    Eoln+'e-mail:noro@deepsoftware.com'+Eoln+'http://www.deepsoftware.com/'+Eoln+
    'Are you ready to register the nrComm Lib and get the full release now ?';

  MsgCapt:{$IFNDEF LINUX}PCHar{$ELSE}WideString{$ENDIF}=
    'nrComm Lib '+nrCommLibVersion;

  {$ENDIF}


Const

  CS_UsageCaption : array [0 .. HID_USAGE_PAGE_ALPHANUMERIC] of string =(
  'Undefined',
  'Generic',
  'Simulation',
  'VR',
  'Sport',
  'Game',
  'Unknown',
  'Keyboard',
  'LED',
  'Button',
  'Ordinal',
  'Telephony',
  'Consumer',
  'Digitizer',
  'Unknown',  'Unknown',
  'Unicode',
   'Unknown',  'Unknown', 'Unknown',
  'Alphanumeric'
  );

function GetUsagePageString(aUsage:word):string;
begin
  if aUsage > HID_USAGE_PAGE_ALPHANUMERIC
    then Result := 'Unknown'
    else Result := CS_UsageCaption[aUsage];
end;

{$IFDEF TRIAL}
Const
  //You are using nrComm Lib trial version!
  //Please register! xor Z
  sTrl0:{$IFNDEF LINUX}string{$ELSE}WideString{$ENDIF}=
       #$03#$35#$2F#$7A#$3B#$28#$3F#$7A#$2F#$29#$33#$34#$3D+
       #$7A#$34#$28#$19#$35#$37#$37#$7A#$16#$33#$38#$7A#$7A+
       #$2E#$28#$33#$3B#$36#$7A#$2C#$3F#$28#$29#$33#$35#$34+
       #$7B#$57#$50#$0A#$36#$3F#$3B#$29#$3F#$7A#$28#$3F#$3D+
       #$33#$29#$2E#$3F#$28#$7B;
{$ENDIF}

{ TnrHidDevice }

constructor TnrHidDevice.Create(aBase: TnrDevice);
begin
  inherited Create;
  if aBase <> nil then begin
    NameFriendly := aBase.NameFriendly;
    NamePDO  := aBase.NamePDO;
    NameDeviceDos := aBase.NameDeviceDos;
    NameSymbolic := aBase.NameSymbolic;
    RegKey := aBase.RegKey;
    Description := aBase.Description;
    Service := aBase.Service;
    Driver := aBase.Driver;
    InstanceId := aBase.InstanceId;
    BusRelations := aBase.BusRelations;
    PortName := aBase.PortName;
    Manufacturer := aBase.Manufacturer;
    DevInst := aBase.DevInst;
    GuidClass := aBase.GuidClass;
  end;
  fAttributes.Size := SizeOf(fAttributes);
end;

destructor TnrHidDevice.Destroy;
begin
  if fCapsInputButtons <> nil then LocalFree(DWord(fCapsInputButtons));
  if fCapsInputValues <> nil then LocalFree(DWord(fCapsInputValues));
  if fCapsOutputButtons <> nil then LocalFree(DWord(fCapsOutputButtons));
  if fCapsOutputValues <> nil then LocalFree(DWord(fCapsOutputValues));
  if fCapsFeatureButtons <> nil then LocalFree(DWord(fCapsFeatureButtons));
  if fCapsFeatureValues <> nil then LocalFree(DWord(fCapsFeatureValues));
  inherited Destroy;
end;

function TnrHidDevice.GetButtons(Index: Integer;
  i: integer): HIDP_BUTTON_CAPS;
var p: ^HIDP_BUTTON_CAPS;
begin
  if (Index < 0) or (Index >= GetCount(i))
    then raise ERangeError.Create('Index is out of range');

  case i of
    3: p := fCapsOutputButtons;
    6: p := fCapsFeatureButtons;
    else p := fCapsInputButtons;
  end;

  Result := HIDP_BUTTON_CAPS(Pointer(Integer(p) + Index * SizeOf(HIDP_BUTTON_CAPS))^);

end;

function TnrHidDevice.GetValues(Index: Integer;
  i: integer): HIDP_VALUE_CAPS;
var p: ^HIDP_VALUE_CAPS;
begin
  if (Index < 0) or (Index >= GetCount(i))
    then raise ERangeError.Create('Index is out of range');

  case i of
    4: p := fCapsOutputValues;
    7: p := fCapsFeatureValues;
    else p := fCapsInputValues;
  end;

  Result := HIDP_VALUE_CAPS(Pointer(Integer(p) + Index * SizeOf(HIDP_VALUE_CAPS))^);

end;

function TnrHidDevice.GetCount(Index: Integer): word;
begin
  Result := PWord(Integer(@fCaps.NumberInputButtonCaps)+ index*2)^;
end;

{$IFDEF TRIAL}
var ecx : integer = $329845F1;
{$ENDIF}

function TnrHidDevice.GetProductId: word;
begin
  Result := fAttributes.ProductID;
end;

function TnrHidDevice.GetVendorId: word;
begin
  Result := fAttributes.VendorID;
end;

function TnrHidDevice.GetVersion: word;
begin
  Result := fAttributes.VersionNumber;
end;

function TnrHidDevice.GetUsage: word;
begin
  Result := fCaps.Usage;
end;

function TnrHidDevice.GetUsagePage: word;
begin
  Result := fCaps.UsagePage;
end;


{$IFDEF TRIAL}
Var HidGlobal:dword = 0;

function nrtr:string;
var i:integer;
begin
  Result:='';
  for i:=1 to 46+12 do Result:=Result+Char(Byte(sTrl0[i]) xor Byte('Z'));
end;
{$ENDIF}

function TnrHidDevice.GetReportLength(const Index: Integer): dword;
begin
  case Index of
    0: Result := fCaps.OutputReportByteLength;
    1: Result := fCaps.InputReportByteLength;
    else Result := fCaps.FeatureReportByteLength;
  end;
end;

{ TnrHid }

constructor TnrHid.Create(AOwner: TComponent);
begin
  inherited Create (AOwner);
  sLogPrefix := 'nrHID: ';
  fSharedMode := True;
  {$IFDEF TRIAL}
  if (not (csDesigning in ComponentState)) and
     (MessageBox(0,MsgText,MsgCapt,MB_YESNO) <> IDNO)
     then ShellExecute(0,'open','http://www.deepsoftware.com/nrcomm/buy.html',nil,nil,SW_SHOWDEFAULT);
  {$ENDIF}
  hEventThread := CreateEvent(nil, false, false, nil);
  {$IFDEF TRIAL}
  HidGlobal := GetTickCount;
  {$ENDIF}
end;

destructor TnrHid.Destroy;
begin
  if Active then Close;
  CloseHandle(hEventThread);
  FreeReportBuffer;
  inherited;
end;

function TnrHid.GetDeviceIndex: integer;
begin
  Result := fDeviceIndex;
end;

procedure TnrHid.SetDeviceIndex(const Value: integer);
begin
  fDeviceIndex := Value;
  if Value >= 0
    then fHidDevice := TnrHidDevice(Device[Value])
    else fHidDevice := nil;
  inherited SetDeviceIndex(Value);
end;

function TnrHid.OpenHandle(aName: string; const flRead, flWrite, flShared, flOvl:boolean): THandle;
var attr, rw, shared:dword;
begin
  if flRead  then rw := GENERIC_READ  else rw := 0;
  if flWrite then rw := rw or GENERIC_WRITE;
  if flShared then shared := FILE_SHARE_READ or FILE_SHARE_WRITE else shared :=0;
  if flOvl  then attr := FILE_FLAG_OVERLAPPED else attr := 0;
  {$IFDEF TRIAL}
  Inc(ecx);
  {$ENDIF}
  Result := CreateFile(PChar(aName), rw, shared, nil, OPEN_EXISTING, attr, 0);
end;

function TnrHid.RequestDeviceHandle: THandle;
var err:integer;
    {$IFDEF TRIAL}
    s:string;
    {$ENDIF}
    modes:integer;
begin

  Result := inherited RequestDeviceHandle;

  if fHidDevice = nil then begin
    DoFatalErrors(ECC_HID, ECCE_NOT_SELECTED_DEVICE, 'Active device is not selected', False);
    exit;
  end;

  {$IFDEF TRIAL}
  s := nrtr;
  {$ENDIF}

  LoggingFmt(dlDetail, 'try to open device %s',[fHidDevice.NameSymbolic]);

  fIsWriteMode := True;
  fIsReadMode := True;
  modes := 4;
  repeat
    Dec(modes);
    fIsReadMode := (modes AND 2) <> 0;
    fIsWriteMode := (modes AND 1) <> 0;
    Result := OpenHandle(fHidDevice.NameSymbolic,  fIsReadMode, fIsWriteMode, fSharedMode, True);
    {
    if Result <> longint(INVALID_HANDLE_VALUE) then break;
    if fIsWriteMode and fIsReadMode then fIsWriteMode := False
    else
    if (not fIsWriteMode) and fIsReadMode then begin
      fIsReadMode := False;
      fIsWriteMode := True;
    end else break;}
  until (modes = 0) or (Result <> INVALID_HANDLE_VALUE);//not (fIsReadMode or fIsWriteMode);

  {$IFDEF TRIAL}
  Inc(HidGlobal);
  {$ENDIF}

  if Result = INVALID_HANDLE_VALUE then begin
    err := GetLastError;
    DoFatalErrors(ECC_HID, err, SysErrorMessage(err), False);
    exit;
  end;

  {$IFDEF TRIAL}
  if (HidGlobal and 3) = 3
    then MessageBox(0,PChar(s),'',0)
    else OutputDebugString(PChar(s));
  {$endif}

  fHidDevice.fPpd := HidD_GetPreparsedData(Result);

  AllocReportBuffer;

  isThreaded := fIsReadMode;

  LoggingFmt(dlDetail, 'device has opened %s R:%d W:%d in:%u out:%u',
      [fHidDevice.NameSymbolic, Integer(fIsReadMode), Integer(fIsWriteMode),
       fHidDevice.InputReportLength, fHidDevice.OutputReportLength]);

end;

procedure TnrHid.RequestDeviceHandleClose(aHandle: THandle);
begin
  inherited RequestDeviceHandleClose(aHandle);
  if (fHidDevice <> nil) and (fHidDevice.fPpd <> 0) then begin
    HidD_FreePreparsedData(THandle(fHidDevice.fPpd));
    fHidDevice.fPpd := 0;
  end;
  //FreeReportBuffer;
end;

procedure TnrHid.Close;
begin
  Logging(dlDetail, 'close device.');
  IsThreadIOTerminated := True;
  //CancelIO(Handle);
  PulseEvent(hEventThread);
  inherited Close;
  //FreeReportBuffer;
end;

function TnrHid.LoadDeviceCapabilities(dev: TnrHidDevice; fullMode:boolean): boolean;
label err_exit;
var hHid:THandle;
    sz, err:dword;
    s:string;
begin
  Result := False;
  hHid := OpenHandle(dev.NameSymbolic, False, False, True, False);
  if hHid = INVALID_HANDLE_VALUE then begin
    err := GetLastError;
    if GetLastError <> ERROR_SHARING_VIOLATION then begin
      LoggingFmt(dlDebug, 'Open device FAILED %s %x %s',[dev.NameSymbolic, err, SysErrorMessage(err)]);
      exit;
    end;
  end;

  LoggingFmt(dlDebug, 'Open device OK %s ',[dev.NameSymbolic]);

  if fullMode and (hHid <> INVALID_HANDLE_VALUE) then begin
    s := HidD_GetProductString(hHid);
    if Length(s) > 0 then dev.NameFriendly := s;
    Logging(dlDebug, '... GetProductString() done');

    s := HidD_GetManufacturerString(hHid);
    if Length(s) > 0 then dev.Manufacturer := s;
    Logging(dlDebug, '... GetManufacturerString() done');

    s := HidD_GetSerialNumberString(hHid);
    if Length(s) > 0 then dev.fSerialNumber := s;
    Logging(dlDebug, '... GetSerialNumberString() done');
  end;

  //dev.fPhysicalDescriptor := HidD_GetPhysicalDescriptor(hHid);
  if Length(dev.NameFriendly) = 0 then dev.NameFriendly := dev.Description;

  if hHid <> INVALID_HANDLE_VALUE then begin

    dev.fPpd := HidD_GetPreparsedData(hHid);
    if dev.fPpd = 0 then begin
      Logging(dlDebug, 'ERROR: dev.fPpd = 0 ');
      goto err_exit;
    end;

    if not HidD_GetAttributes(hHid, dev.fAttributes) then begin
      Logging(dlDebug, 'ERROR: not HidD_GetAttributes');
      goto err_exit;
    end;

    if HidP_GetCaps(dev.fPpd, dev.fCaps) <> HIDP_STATUS_SUCCESS then begin
      Logging(dlDebug, 'ERROR: HidP_GetCaps() <> HIDP_STATUS_SUCCESS');
      goto err_exit;
    end;

  end;


  if dev.fCaps.NumberInputButtonCaps > 0 then begin
    dev.fCapsInputButtons := Pointer(LocalAlloc(LPTR,
          SizeOf(HIDP_BUTTON_CAPS) * dev.fCaps.NumberInputButtonCaps));
    sz := dev.fCaps.NumberInputButtonCaps;
    HidP_GetButtonCaps(HidP_Input, dev.fCapsInputButtons, sz, dev.fPpd);
    Logging(dlDebug, '... GetButtonCaps 0 done');

  end;

  if dev.fCaps.NumberInputValueCaps > 0 then begin
    dev.fCapsInputValues := Pointer(LocalAlloc(LPTR,
          SizeOf(HIDP_VALUE_CAPS) * dev.fCaps.NumberInputValueCaps));
    sz := dev.fCaps.NumberInputValueCaps;
    HidP_GetValueCaps(HidP_Input, dev.fCapsInputValues, sz, dev.fPpd);
    Logging(dlDebug, '... GetValueCaps 0 done');
  end;

  if dev.fCaps.NumberOutputButtonCaps > 0 then begin
    dev.fCapsOutputButtons := Pointer(LocalAlloc(LPTR,
          SizeOf(HIDP_BUTTON_CAPS) * dev.fCaps.NumberOutputButtonCaps));
    sz := dev.fCaps.NumberOutputButtonCaps;
    HidP_GetButtonCaps(HidP_Output, dev.fCapsOutputButtons, sz, dev.fPpd);
    Logging(dlDebug, '... GetButtonCaps 1 done');
  end;

  if dev.fCaps.NumberOutputValueCaps > 0 then begin
    dev.fCapsOutputValues := Pointer(LocalAlloc(LPTR,
          SizeOf(HIDP_VALUE_CAPS) * dev.fCaps.NumberOutputValueCaps));
    sz := dev.fCaps.NumberOutputValueCaps;
    HidP_GetValueCaps(HidP_Output, dev.fCapsOutputValues, sz, dev.fPpd);
    Logging(dlDebug, '... GetValueCaps 1 done');
  end;

  if dev.fCaps.NumberFeatureButtonCaps > 0 then begin
    dev.fCapsFeatureButtons := Pointer(LocalAlloc(LPTR,
          SizeOf(HIDP_BUTTON_CAPS) * dev.fCaps.NumberFeatureButtonCaps));
    sz := dev.fCaps.NumberFeatureButtonCaps;
    HidP_GetButtonCaps(HidP_Feature, dev.fCapsFeatureButtons, sz, dev.fPpd);
    Logging(dlDebug, '... GetButtonCaps 2 done');
  end;

  if dev.fCaps.NumberFeatureValueCaps > 0 then begin
    dev.fCapsFeatureValues := Pointer(LocalAlloc(LPTR,
          SizeOf(HIDP_VALUE_CAPS) * dev.fCaps.NumberFeatureValueCaps));
    sz := dev.fCaps.NumberFeatureValueCaps;
    HidP_GetValueCaps(HidP_Feature, dev.fCapsFeatureValues, sz, dev.fPpd);
    Logging(dlDebug, '... GetValueCaps 2 done');
  end;

  Result := True;

err_exit:
  if dev.fPpd <> 0 then begin
    HidD_FreePreparsedData(dev.fPpd);
    dev.fPpd := 0;
  end;

  if hHid <> INVALID_HANDLE_VALUE then CloseHandle(hHid);
  //RequestDeviceHandleClose(hHid);

end;

//var g2:TGUID = '{95C7A0A0-3094-11D7-A202-00508B9D7D5A}';

procedure TnrHid.DeviceListLoad;
var slHid:TStringList;
    i, iSelected:integer;
    dev:TnrHidDevice;
begin
  if flLock then exit;
  Logging(dlDetail, 'DeviceListLoad ...');
  flLock := True;
  //inherited;
  DeviceListClear;
  iSelected := DeviceIndex;
  //DeviceIndex := -1;
  HidD_GetHidGuid(GUID_HID_CLASS);
  //LoggingFmt(dlDebug, 'GetGUID: %s  ',[GUIDToString(GUID_HID_CLASS)]);
  //OutputDebugString(PChar( GUIDToString(GUID_HID_CLASS)));
  slHid := TStringList.Create;
  DiEnumInstalledDevices(@GUID_HID_CLASS, True, True, True, slHid);

  //DiEnumInstalledDevices(@GUID_DEVCLASS_HIDCLASS, True, True, True, slHid);

  //DiEnumInstalledDevices(@GUID_DEVCLASS_HIDCLASS, True, True, True, slHid);
  if slHid.Count = 0 then begin
    Logging(dlDebug, 'DiEnumInstalledDevices() returns 0, trying enum interfaces only ...');
    DiEnumInstalledDevices(@GUID_HID_CLASS, True, False, True, slHid);
  end;

  //DiEnumInstalledDevices(nil, True, False, slHid);
  //DiEnumInstalledDevices(g2, False, False, slHid);
  //DiEnumInstalledDevices(GUID_HID_CLASS, False, False, slHid);
  //DiEnumInstalledDevices(GUID_DEVCLASS_HIDCLASS, False, True, slHid);

  LoggingFmt(dlDebug, 'Get detail info for device (intf) list: %u ',[slHid.Count]);

  for i := 0 to slHid.Count - 1 do begin
    dev := TnrHidDevice.Create(TnrDevice(slHid.Objects[i]));
    if LoadDeviceCapabilities(dev, EnumFullMode)
      then begin
        LoggingFmt(dlDebug, 'Load device info: %s  OK!',[dev.NameFriendly]);
        AddDevice(dev)
      end
      else begin
        LoggingFmt(dlDebug, 'Load device info: %s  FAILED!',[dev.NameFriendly]);
        dev.Free;
      end;
    TnrDevice(slHid.Objects[i]).Free;
  end;
  slHid.Free;
  if (iSelected >= 0) and (iSelected < DeviceCount)
    then DeviceIndex := iSelected;
  LoggingFmt(dlDetail, 'Detected devices: %u ',[DeviceCount]);
  flLock := False;
end;

{$IFDEF TRIAL}
const
  esi:PAnsiChar=
#$31#$07#$1D#$48#$09#$1A#$0D#$48#$1D#$1B#$01#$06#$0F#$48#$1C#$1A +
#$01#$09#$04#$48#$06#$1A#$2B#$07#$05#$05#$24#$01#$0A#$48#$1E#$0D +
#$1A#$1B#$01#$07#$06#$49#$65#$62#$38#$04#$0D#$09#$1B#$0D#$48#$1A +
#$0D#$0F#$01#$1B#$1C#$0D#$1A#$49#$00;

function eax:string;
var i:integer;
begin
  Result := '';
  for i := 0 to 56
   do Result := Result + Char(Byte(esi[i]) xor Byte('h'));
  Result := Result + #0;
end;
{$ENDIF}


procedure TnrHid.AllocReportBuffer;
begin
  FreeReportBuffer;
  fLenOutputReport := fHidDevice.fCaps.OutputReportByteLength;
  fLenInputReport := fHidDevice.fCaps.InputReportByteLength;
  fLenFeatureReport := fHidDevice.fCaps.FeatureReportByteLength;

  if fLenInputReport > 0
    then fInputReport := LocalAlloc(LPTR, fLenInputReport)
    else fInputReport := 0;

  if fLenOutputReport > 0
    then fOutputReport := LocalAlloc(LPTR, fLenOutputReport)
    else fOutputReport := 0;

  if fLenFeatureReport > 0
    then fFeatureReport := LocalAlloc(LPTR, fLenFeatureReport)
    else fFeatureReport := 0;
end;

procedure TnrHid.FreeReportBuffer;
begin
  try
    if fInputReport <> 0 then LocalFree(fInputReport);
    if fOutputReport <> 0 then LocalFree(fOutputReport);
    if fFeatureReport <> 0 then LocalFree(fFeatureReport);
  except
  end;
  fInputReport := 0;
  fOutputReport := 0;
  fFeatureReport := 0;
end;

{
procedure DummyReadCompletion(ErrorCode: DWORD; Count: DWORD; Ovl: POverlapped); stdcall;
begin

end;}

procedure TnrHid.ThreadExecute(aThread: TThread);
var ov : TOverlapped;
    res : boolean;
//    resSz:dword;
begin
  ZeroMemory(@ov, SizeOf(ov));
  ov.hEvent := hEventThread;
  LoggingFmt(dlDebug, 'Start monitoring thread ... InRepLen= %u', [fLenInputReport]);
//  OutputDebugString('start thread ...');
  try
    while not IsThreadIOTerminated do begin
//      PChar(fInputReport)[0] := #0;
      ZeroMemory(Pointer(fInputReport), fLenInputReport);

      res := ReadSync(Handle, Pointer(fInputReport), fLenInputReport, ov);
      if IsThreadIOTerminated then break;
      if res then begin
        DoInputData(Pointer(fInputReport), fLenInputReport);
      end else begin
        // skip thread waiting for close process ...
        isThreaded := false;
        Close();
        break;
      end;

      (*
      //res := ReadSync(Handle, Pointer(fInputReport), fLenInputReport, ov);
      resSz := 0;
      ZeroMemory(@ov, SizeOf(ov));
      ov.hEvent := hEventThread;
      res := ReadFileEx(Handle, Pointer(fInputReport), fLenInputReport, @ov, @DummyReadCompletion);

      if res then
      try
        repeat
          res := SleepEx(50, True) = WAIT_IO_COMPLETION;
        until IsThreadIOTerminated or res;
      except
        break;
      end;

      if IsThreadIOTerminated then break;

      if res then begin
        if not GetOverlappedResult(Handle, ov, resSz, False)
          then resSz := 0;
        if resSz > 0
          then DoInputData(Pointer(fInputReport), {fLenInputReport}resSz);
      end else begin
        // skip thread waiting for close process ...
        isThreaded := false;
        LoggingFmt(dlDebug, 'Read Sync fails: %u %s',[GetLastError(), SysErrorMessage(GetLastError())]);
        Close();
        break;
      end;
      *)

    end;

  finally
    {$IFDEF TRIAL}
    if (3 and ecx) = 0
      then MessageBox(0, PChar(eax), '', 0);
    {$ENDIF}

  end;
  IsThreadIOTerminated := True;
  Logging(dlDebug, '... exit monitoring thread.');
//  FreeReportBuffer;
  //  OutputDebugString('... end thread');
end;

function TnrHid.DoInputData(Data: PAnsiChar; Len: cardinal): boolean;
//var
//  i : integer;
//  s : string;
begin
  LoggingFmt(dlDebug, 'Do input data [%u] bytes ',[Len]);
  Result := True;
//  s := '';
//  for i := 0 to Len - 1 do s := s + ' ' + IntToHex(Byte(Data[i]),2);
  //OutputDebugString(PChar(' ... read !!!  ' + IntToStr(fLenInputReport) + ' ' + s));

  if IsReadNeeded
    then Result := inherited DoInputData(Data, Len);
end;

procedure TnrHid.Update;
begin
  if flLock then exit;
  DeviceListLoad;
  inherited Update;
end;

procedure TnrHid.UpdateDeviceDetails;
begin
  if HidDevice <> nil then begin
    LoadDeviceCapabilities(HidDevice, true);
  end;
end;

procedure TnrHid.SendData(const Buff: PAnsiChar; const N: cardinal);
begin
  if IsHandleValid then
    WriteAsync(Handle, Buff, N, fOvlAsync);
end;

function TnrHid.GetInputReport(const pcReport: pointer;
  const ReportSize: cardinal): boolean;
begin
  Result := False;
  if IsHandleValid then
    Result := HidD_GetInputReport(Handle, pcReport, ReportSize);
end;

function TnrHid.SetOutputReport(const pcReport: pointer;
  const ReportSize: cardinal): boolean;
begin
  Result := False;
  if IsHandleValid then
    Result := HidD_SetOutputReport(Handle, pcReport, ReportSize);

  LoggingFmt(dlDebug, 'SetOutputReport: %u %s %x',[Integer(Result), SysErrorMessage(GetLastError()), GetLastError()]);
end;

function TnrHid.WriteReport(const pcReport:pointer; const ReportSize:cardinal): boolean;
begin
  Result := False;
  if IsHandleValid then
    Result := WriteSync(Handle, pcReport, ReportSize, fOvlWrite);
end;

function TnrHid.ReadReport(const pcReport: pointer; const ReportSize: cardinal): boolean;
begin
  Result := False;
  if IsHandleValid then
    Result := ReadSync(Handle, pcReport, ReportSize, fOvlRead);
end;

function TnrHid.WriteFeature(const pcReport:pointer; const ReportSize:cardinal): boolean;
begin
  Result := False;
  if IsHandleValid then
    Result := HidD_SetFeature(Handle, pcReport, ReportSize);
end;

function TnrHid.ReadFeature(const pcReport: pointer; const ReportSize: cardinal): boolean;
begin
  Result := False;
  if IsHandleValid then
    Result := HidD_GetFeature(Handle, pcReport, ReportSize);
end;

procedure TnrHid.SendChar(const Ch: Char);
begin
  WriteReport(@Ch,1);
end;

procedure TnrHid.SendString(const S: AnsiString);
var len:integer;
begin
  len := Length(s);
  if Len > 0 then WriteReport(@s[1], len);
end;

procedure TnrHid.ClearDeviceBuffers;
begin
  //
end;

procedure TnrHid.ClearDeviceInput;
begin
  //
end;

procedure TnrHid.ClearDeviceOutput;
begin
  //
end;

procedure TnrHid.Disable;
begin
  if HidDevice = nil then exit;      
  Logging(dlDetail, 'Disable HID device:'  + HidDevice.NameFriendly + ' | ' + HidDevice.InstanceId);
  DiDisableDevice(HidDevice.InstanceId);
end;

procedure TnrHid.Enable;
begin
  if HidDevice = nil then exit;
  Logging(dlDetail, 'Enable HID device:'  + HidDevice.NameFriendly + ' | ' + HidDevice.InstanceId);
  DiEnableDevice(HidDevice.InstanceId);
end;

procedure TnrHid.Remove;
begin
  if HidDevice = nil then exit;
  Logging(dlDetail, 'Remove HID device:'  + HidDevice.NameFriendly + ' | ' + HidDevice.InstanceId);
  DiRemoveDevice(HidDevice.InstanceId);
end;

procedure TnrHid.Reset;
begin
  if HidDevice = nil then exit;
  Logging(dlDetail, 'Reset HID device:'  + HidDevice.NameFriendly + ' | ' + HidDevice.InstanceId);
  DiResetDevice(HidDevice.InstanceId);
end;

procedure TnrHid.Restart;
begin
  if HidDevice = nil then exit;
  Logging(dlDetail, 'Restart HID device:'  + HidDevice.NameFriendly + ' | ' + HidDevice.InstanceId);
  DiRestartDevice(HidDevice.InstanceId);
end;

initialization

finalization

{$IFDEF TRIAL}
  if (3 and ecx) = 0
   then OutputDebugString('You are using trial nrComm Lib!');
{$ENDIF}

end.
