{*******************************************************}
{                                                       }
{       nrCommLib Communication Library for Delphi      }
{       TAPI implementation (VCL)                       }
{       Copyright (c) 1997-2011 Roman Novgorodov        }
{       v9.27                                           }
{       last update: 19 Jan 2009                        }
{                                                       }
{       e-mail:noro@deepsoftware.com                    }
{       DeepSoftware                                    }
{       http://www.deepsoftware.com/                    }
{                                                       }
{*******************************************************}
{$I nrcommlib.inc}
{$IMPORTEDDATA ON}

unit nrcommtapi;

interface

uses nrclasses,nrcomm,nrtapi, nrwin32,
    {$IFDEF NR_WITH_CONTROLS}
    Forms,
    {$ENDIF}
    Graphics, Windows, Messages, registry, mmsystem,
    SysUtils, Classes;

Const

  //ArrrayTonesCount=3;

  //type of error codes for TnrCommTAPI
  //ErrorTypeTAPI  = 3;

  // error codes about TAPI errors ...
  ntecDLLError    = 1;
  ntecNoDevices   = 2;
  ntecInitTAPI    = 3;
  ntecVerTAPI     = 4;
  ntecLineOpen    = 5;
  ntecCall        = 6;
  ntecWaitCall    = 7;
  ntecDeviceIndex = 8;
  ntecAnswer      = 9;
  ntecVoiceFeature= 10;

  // string messages about TAPI errors ...
  nteDLLError    = 'nrTAPI Fatal Error: Load TAPI dll fault';
  nteNoDevices   = 'nrTAPI Fatal Error: no one TAPI devices was found';
  nteInitTAPI    = 'nrTAPI Fatal Error: Initialize Fatal Error';
  nteVerTAPI     = 'nrTAPI Fatal Error: lineNegotiateAPIVersion';
  nteLineOpen    = 'nrTAPI Error: Line was not open';
  nteCall        = 'nrTAPI Error: Call was fault';
  nteWaitCall    = 'nrTAPI Error: Waiting for a incoming call was fault';
  nteDeviceIndex = 'Device index (%d of %d) is not valid';
  nteAnswer      = 'nrTAPI Error: Aswer on the incoming call was fault';
  nteVoiceFeature  = 'nrTAPI Error: Device does not support Voice features';

type

  TnrCommTAPI = class;//forward declaration ...

  TnrCustomTAPIProcessor=class(TnrCustomDataProcessor)
  private
    procedure SetCommTAPI(const Value: TnrCommTAPI);
    function GetCommTAPI: TnrCommTAPI;
  protected
    property nrCommTAPI:TnrCommTAPI read GetCommTAPI write SetCommTAPI;
  end;


  TNotifyCall=procedure (Com:TObject;hDevice,Msg,Par1,Par2,Par3:dword)of object;
  TNotifyCallDisconnect= procedure (Com:TObject;ByRemote:boolean)of object;
  TOnLineError=procedure (Com:TObject;ErrorCode:cardinal{;s:string})of object;
  TOnLineCallState=procedure (Com:TObject;StateCode,DetailCode:cardinal)of object;
  TNotifyRing=procedure (Com:TObject;var Ignore:boolean)of object;
  TOnDTMFEvent=procedure (Com:TObject;Digit:char)of object;

  TnrDeviceTAPI = class(TnrDevice)
//    Handle:longint;
  public
    IndexTapi:integer;
    DeviceName:string;
    PortName:string;
    WaveInId:integer;
    WaveOutId:integer;
    Modes:integer;
    MediaModes:integer;
    infDevCaps:TLINEDEVCAPS;
  end;

  TSpeakVol=(svLow,svMedium,svHigh);
  TSpeakMode=(smOff,smCallSetup,smOn,smDial);
  TModemOptions=set of(moPreTerminal,moPostTerminal,moShowIcon,
     moErrorControl,moCompression,moForcedConnection,moWaitTone);
  TVoiceFormat=(vf11m8,vf11s8,vf11m16,vf11s16,
                vf22m8,vf22s8,vf22m16,vf22s16,
                vf44m8,vf44s8,vf44m16,vf44s16,
                vf48m8,vf48s8,vf48m16,vf48s16,
                vf96m8,vf96s8,vf96m16,vf96s16);
  TVoiceFormats=set of TVoiceFormat;

  TnrModemDevice=class(TPersistent)
  private
    fInactivityTimeout: cardinal;
    fCallFailTimer: cardinal;
    fSpeakMode: TSpeakMode;
    fSpeakVol: TSpeakVol;
    fOptions:TModemOptions;
    fOptionsOfConnect:TModemOptions;
    DevCfg:^TModemDeviceConfig;
    fDeviceIndex:integer;
    fBaudRateOfConnection:cardinal;
    fBaudRateMax:cardinal;
    fFlowControl:TStreamProtocol;
    fFlowControlOfConnect:TStreamProtocol;
    nrTAPI:TnrCommTAPI;
    wavInfOut:PWAVEOUTCAPS;
    wavInfIn:PWAVEINCAPS;
    function GetSpeakerMode: TSpeakMode;
    function GetSpeakVol: TSpeakVol;
    function GetTimeSetting(Index:integer): cardinal;
    procedure SetSpeakerMode(const Value: TSpeakMode);
    procedure SetSpeakVol(const Value: TSpeakVol);
    procedure SetTimeSetting(const Index: Integer; const Value: cardinal);
    function GetOptions(Index:integer): TModemOptions;
    procedure SetOptions(const Index: Integer; const Value: TModemOptions);
    function GetFlowControl(Index:integer): TStreamProtocol;
    procedure SetFlowControl(const Index: Integer; const Value: TStreamProtocol);
    function GetVoice(const Index:integer): boolean;
    function GetVoiceFormats(const Index:integer): TVoiceFormats;
    function GetInitString: string;
    procedure SetInitString(const Value: string);
    function GetVoiceID(const Index: Integer): integer;
    procedure SetDeviceIndex(const Value: integer);
    property DeviceIndex:integer read fDeviceIndex write SetDeviceIndex;
  public
    sRegKey:string;
//    sRegModemRoot:string;
//    sRegModemEnumRoot:string;
    constructor Create(AOwner:TnrCommTAPI);
    destructor Destroy; override;
    procedure Assign(Source: TPersistent);override;
    procedure SaveProperties;//save published prop into internal tapi struc
    procedure LoadProperties;//load published prop from internal tapi struc
    property VoiceOut:boolean index 0 read GetVoice;
    property VoiceIn:boolean index 1 read GetVoice;
    property VoiceOutId:integer index 0 read GetVoiceID;
    property VoiceInId:integer index 1 read GetVoiceID;
    property VoiceOutFormats:TVoiceFormats index 0 read GetVoiceFormats;
    property VoiceInFormats:TVoiceFormats index 1 read GetVoiceFormats;
  published
    property InitString:string read GetInitString write SetInitString;
    property SpeakerVol:TSpeakVol read GetSpeakVol write SetSpeakVol;
    property SpeakerMode:TSpeakMode read GetSpeakerMode write SetSpeakerMode;
    property InactivityTimeout:cardinal index 0 read GetTimeSetting write SetTimeSetting;
    property CallFailTimer:cardinal index 1 read GetTimeSetting write SetTimeSetting;
    property BaudRateOfConnect:cardinal index 2 read GetTimeSetting;
    property BaudRateMax:cardinal index 3 read GetTimeSetting write SetTimeSetting;
    property Options:TModemOptions index 0 read GetOptions write SetOptions;
    property OptionsOfConnect:TModemOptions index 1 read GetOptions;
    property FlowControl:TStreamProtocol index 0 read GetFlowControl write SetFlowControl;
    property FlowControlOfConnect:TStreamProtocol index 1 read GetFlowControl;
  end;

  TTapiState=(ctsNeutral,ctsCall,ctsWait);
  TTapiCallState=(tcsClosed,tcsClosing,tcsCalling,tcsWaiting,
                  tcsAnswering,tcsConnecting,tcsConnected);

  TnrCommTAPI = class(TnrCustomComm)
  private    { Private declarations }
    fEventChar:char;
    hLineApp,hLine,hCall:longint;
    NumDevices:longint;
    APIVersion:longint;
    fRingsBeforeAnswer:integer;
    fRingCount:integer;
    LineParams:TLineCallParams;
    fPhone: string;
    fCountryCode: integer;
    fAreaCode: string;
    fOnLineBusy: TNotifyEvent;
    fOnLineConnected: TNotifyEvent;
    fOnLineDoingConnection: TNotifyEvent;
    fOnLineDisconnected: TNotifyCallDisconnect;
    fDeviceIndex: integer;
    fOnLineError: TOnLineError;
    fRetries: integer;
    fCurrentRetries:integer;
    PauseTimer:TnrTimer;
    fTapiState:TTapiState;
    fCallState:TTapiCallState;
    fPulse: boolean;
    fIcon: TIcon;
    fOnCallState: TOnLineCallState;
    fModem: TnrModemDevice;
    LastTAPIResult:integer;
    flReply:integer;
    ReplyResult:integer;
    ReplyId:integer;
    dwInUse:cardinal;
    fVoice: boolean;
    fCurrentCallIsVoice:boolean;
    fOnDTMF: TOnDTMFEvent;
    pcPhone:PChar;
    fAddressID: integer;
    fDisplayableNumber: string;
    fDialableNumber: string;
    fOnTone: TNotifyEvent;

    CallInfo:PLineCallInfo;
    fCallerID:string;
    fCallerIDName: string;
    fCalledID:string;
    fCalledIDName: string;
    fOnRing: TNotifyRing;
    hEvent:dword;

    //fCallerIDStatus: string;
    //ArrrayTones:array [0..ArrrayTonesCount-1] of TLineMonitorTone;
    procedure AllocTAPI;
    procedure FreeTAPI;
    procedure NecessaryAllocTAPI;
    procedure NecessaryFreeTAPI;
    procedure CloseLine;
    procedure HandleRing(hInCall:longint);
    procedure HandleConnection(hInCall:longint);
    procedure HandleDisconnection(hInCall:longint;ByRemote:boolean);
    procedure HandleProceeding;
    function GetDeviceTAPI(Index: integer): TnrDeviceTAPI;
    function GetActive: boolean;
    procedure DoPauseTimer(Sender:TObject);
    function GetPauseOnRepeat: integer;
    procedure SetPauseOnRepeat(const Value: integer);
    function GetTrueDeviceIndex: integer;
    function GetIcon: TIcon;
    procedure SetDeviceModem(const Value: TnrModemDevice);
    procedure SetVoice(Value: boolean);
    procedure SetAreaCode(const Value: string);
    function GetCanonical: string;
    function GetPhone: string;
    function GetRetries: integer;
    function GetRingsBeforeAnswer: integer;
    function GetAreaCode: string;
    function GetCountryCode: integer;
    function GetPulse: boolean;
    function GetVoice: boolean;
    function GetOnCallState: TOnLineCallState;
    function GetOnDTMF: TOnDTMFEvent;
    function GetOnLineBusy: TNotifyEvent;
    function GetOnLineConnected: TNotifyEvent;
    function GetOnLineDisconnected: TNotifyCallDisconnect;
    function GetOnLineDoingConnection: TNotifyEvent;
    function GetOnLineError: TOnLineError;
    function GetOnTone: TNotifyEvent;
    procedure SetCountryCode(const Value: integer);
    procedure SetOnCallState(const Value: TOnLineCallState);
    procedure SetOnDTMF(const Value: TOnDTMFEvent);
    procedure SetOnLineBusy(const Value: TNotifyEvent);
    procedure SetOnLineConnected(const Value: TNotifyEvent);
    procedure SetOnLineDisconnected(const Value: TNotifyCallDisconnect);
    procedure SetOnLineDoingConnection(const Value: TNotifyEvent);
    procedure SetOnLineError(const Value: TOnLineError);
    procedure SetOnTone(const Value: TNotifyEvent);
    procedure SetPhone(const Value: string);
    procedure SetPulse(const Value: boolean);
    procedure SetRetries(const Value: integer);
    procedure SetRingsBeforeAnswer(const Value: integer);
    procedure SetActive(const Value: boolean);
  protected    { Protected declarations }
    function GetPortName: string; override;
    function GetDeviceName: string;// override;
    function RequestDeviceHandle:THandle;override;
    procedure InitAfterOpen; override;
    procedure SetEventChar(const Value: char);override;
    function GetEventChar:char;override;
    function GetDeviceIndex: integer; override;
    procedure SetDeviceIndex(const Value: integer);  override;
    procedure DeviceListLoad; override;
    procedure ResetReply;
    function WaitReply(Id:integer;TimeOut:integer):integer;
    procedure HandleReply(Id,Rslt:integer);
    procedure WaitIDLE;
    function IsIDLE:boolean;
    procedure DoFatalTAPIError(cd:integer;s:string);
    procedure UpdateCallInfo(const hACall,dwStatus:cardinal);
    procedure Close;   override;
  public    { Public declarations }
    constructor Create(AOwner: TComponent);override;
    destructor Destroy; override;
    procedure Call;                 virtual;
    procedure HangUp;               virtual;
    procedure WaitForIncomingCall;  virtual;
    procedure DeviceConfigDialog;   virtual;
    procedure Answer;               virtual;
    procedure Update;               override;
    procedure NumberDialog;
    procedure LoadConfig(const Root: HKEY; const Key, Name: string;const flForCurrentDevice:boolean);virtual;
    procedure SaveConfig(const Root: HKEY; const Key, Name: string);virtual;
    procedure GenerateDigits(const Digits:string);
    procedure GenerateTone;
    property TrueDeviceIndex:integer read GetTrueDeviceIndex;
    property Device[Index:integer]:TnrDeviceTAPI read GetDeviceTAPI;
    property RingCount:integer read fRingCount;
    property CurrentRetries:integer read fCurrentRetries;
    property State:TTapiState read fTapiState;
    property CallState:TTapiCallState read fCallState;
    property CanonicalPhone:string read GetCanonical;
    property DialableNumber:string read fDialableNumber;
    property DisplayableNumber:string read fDisplayableNumber;
    property AddressID:integer read fAddressID;
    property ComName;
    property ReadyForRead;
//    property TimeOuts;
    property CommProp;
    property TotalReceived;
    property TotalSent;
//    property EventList;
    property Active:boolean read GetActive write SetActive;
    property Handle;
    property HandleLine:longint read hLine;
    property HandleAppLine:longint read hLineApp;
    property HandleCall:longint read hCall;
    property Icon:TIcon read GetIcon;
    property CallerID:string read fCallerID;// GetCallerID;
    property CallerIDName:string read fCallerIDName;// GetCallerID;
    property CalledID:string read fCalledID;// GetCallerID;
    property CalledIDName:string read fCalledIDName;// GetCallerID;

  published    { Published declarations }
    property Retries:integer read GetRetries write SetRetries;
    property PauseOnRepeat:integer read GetPauseOnRepeat write SetPauseOnRepeat;
    property Phone:string read GetPhone write SetPhone;
    property RingsBeforeAnswer:integer read GetRingsBeforeAnswer write SetRingsBeforeAnswer;
    property CountryCode:integer read GetCountryCode write SetCountryCode;
    property AreaCode:string read GetAreaCode write SetAreaCode;
    property OnLineBusy:TNotifyEvent read GetOnLineBusy write SetOnLineBusy;
    property OnLineConnected:TNotifyEvent read GetOnLineConnected write SetOnLineConnected;
    property OnLineDoingConnection:TNotifyEvent read GetOnLineDoingConnection write SetOnLineDoingConnection;
    property OnLineDisconnected:TNotifyCallDisconnect read GetOnLineDisconnected write SetOnLineDisconnected;
    property OnLineError:TOnLineError read GetOnLineError write SetOnLineError;
    property OnCallState:TOnLineCallState read GetOnCallState write SetOnCallState;
    property OnDTMF:TOnDTMFEvent read GetOnDTMF write SetOnDTMF;
    property OnTone:TNotifyEvent read GetOnTone write SetOnTone;
    property OnRing:TNotifyRing read fOnRing write fOnRing;
    property DeviceIndex:integer read GetDeviceIndex write SetDeviceIndex;
    property Pulse:boolean read GetPulse write SetPulse;
    property DeviceModem:TnrModemDevice read fModem write SetDeviceModem;
    property Voice:boolean read GetVoice write SetVoice;

    {$IFDEF NR_WITH_CONTROLS}
    property Terminal;
    property TerminalUsage;
    property TerminalEcho;
    property MonitorCTS;
    property MonitorDSR;
    property MonitorRLSD;
    property MonitorRing;
    {$ENDIF}

//    property EventListOnLine;
    property TraceStates;
    property ModemSignals;
    property DataProcessor;
    property EventChar;
    property BufferInSize;
    property BufferOutSize;
    property TimeoutRead;
    property TimeoutWrite;
    property Log;
//    property OnListEvent;     // Events ...
    property OnAfterReceive;
    property OnAfterSend;
    property OnTraceState;
    property OnCommError;
    property OnFatalError;
  end;

Var
  //simple list with FriendlyModemNames as Name[]
  // and Registry Key as Values[]
  ModemList:TStringList=nil;


implementation
uses nrsysutils;

Const
  DeviceClassModems='comm/datamodem';
  DeviceClassPort='comm/datamodem/portname';
  DeviceClassWaveIn='wave/in';
  DeviceClassWaveOut='wave/out';
  DeviceClassWaveInOut='wave/in/out';

  ssRegModemKeyWin95='System\CurrentControlSet\Services\Class';
  ssRegModemEnumKeyWin95='Enum\Root\Modem';
  ssRegModemKeyWinNT='SYSTEM\CurrentControlSet\Control\Class\{4D36E96D-E325-11CE-BFC1-08002BE10318}';


Type
  PModemInfo=^TModemInfo;
  TModemInfo=packed record
    vs:TVARSTRING;
    hComm:longint;
    szDeviceName:array[0..254]of AnsiChar;
  end;

(*  TWaitThread = class(TThread)
  private
    nrTapi:TnrCommTAPI;
  public
    constructor Create(aOwner:TnrCommTAPI);
    procedure Execute; override;
  end; *)


// for x64 version of windows the mi.hComm field has 64bit size,
// therefore we must skip first 4 chars at mi.szDeviceName[]
function GetModemCaption(const mi:TModemInfo):PAnsiChar;
var i:integer;
begin
  Result := @mi.szDeviceName[0];
  for i := 0 to 4 do
    if Byte(mi.szDeviceName[i]) <> 0 then begin
      Result := @mi.szDeviceName[i];
      break;
    end;
end;

procedure EnumSubKey(sKey:string);
var hK:HKEY;
    pcKey,pcVal:PChar;
    Idx,Sz,SzV:dword;
    pFt:TFileTime;
    sFn,sDrv:string;
begin
  Sz:=1024;
  SzV:=1024;
  GetMem(pcKey,Sz);
  GetMem(pcVal,Sz);
  sFn:='';
  sDrv:='';
  if RegOpenKeyEx(HKEY_LOCAL_MACHINE,PChar(sKey),0,KEY_ENUMERATE_SUB_KEYS or KEY_QUERY_VALUE,hK)=ERROR_SUCCESS then begin
    if RegQueryValueEx(hK,'Class',nil,nil,PByte(pcVal),@SzV)=ERROR_SUCCESS then begin
      if Lowercase(String(pcVal))='modem' then begin
         SzV:=1024;
         RegQueryValueEx(hK,'FriendlyName',nil,nil,PByte(pcVal),@SzV);
         sFn:=String(pcVal);
         SzV:=1024;
         RegQueryValueEx(hK,'Driver',nil,nil,PByte(pcVal),@SzV);
         ModemList.Add(sFn+'='+ssRegModemKeyWin95+'\'+String(pcVal));
      end;
    end else begin //recursive enumeration ...
      Idx:=0;
      Sz:=1024;
      while (RegEnumKeyEx(hK,Idx,pcKey,Sz,nil,nil,nil,@pFt)=ERROR_SUCCESS) do begin
        sFn:=sKey+'\'+String(pcKey);
        EnumSubKey(sFn);
        Sz:=1024;
        Inc(Idx);
      end;
    end;
    RegCloseKey(hK);
  end;
  FreeMem(pcKey,Sz);
  FreeMem(pcVal,Sz);
end;

function GetRegByModemName(sM:string):string;
var hK,hK2:HKEY;
    pcKey,pcVal:PChar;
    Idx,Sz,SzV:dword;
    pFt:TFileTime;
begin
  if not Assigned(ModemList)
    then ModemList:=TStringList.Create;
  if ModemList.Count=0 then begin  //fill modem list ...
    if Win32Platform=VER_PLATFORM_WIN32_NT then begin
      Sz:=1024;
      GetMem(pcKey,Sz);
      GetMem(pcVal,Sz);
      if RegOpenKeyEx(HKEY_LOCAL_MACHINE,PChar(ssRegModemKeyWinNT),0,KEY_ENUMERATE_SUB_KEYS or KEY_QUERY_VALUE,hK)=ERROR_SUCCESS then begin
        Idx:=0;
        while (RegEnumKeyEx(hK,Idx,pcKey,Sz,nil,nil,nil,@pFt)=ERROR_SUCCESS) do begin
          SzV:=1024;
//          OutputDebugString(PChar(ssRegModemKeyWinNT+' + ' + pcKey));
          if RegOpenKeyEx(hK,pcKey,0,KEY_QUERY_VALUE,hK2)=ERROR_SUCCESS then begin
            if RegQueryValueEx(hK2,'FriendlyName',nil,nil,PByte(pcVal),@SzV)=ERROR_SUCCESS
              then ModemList.Add(String(pcVal)+'='+ssRegModemKeyWinNT+'\'+String(pcKey));
            RegCloseKey(hK2);
          end;
          Sz:=1024;
          Inc(Idx);
        end;
        RegCloseKey(hK);
      end;
      FreeMem(pcKey);
      FreeMem(pcVal);
    end else begin
      EnumSubKey('Enum');
    end;
  end;
  Result:=ModemList.Values[sM]
end;

{$IFDEF TRIAL}
function eax:string; forward;
{$ENDIF}

function GetWaveErrorString(const Code:integer; flOut:boolean):string;
var p:PChar;
begin
  p:=StrAlloc(MAXERRORLENGTH);
  if flOut
   then waveOutGetErrorText(Code,p,MAXERRORLENGTH-1)
   else waveInGetErrorText(Code,p,MAXERRORLENGTH-1);
  Result:=String(p);
  StrDispose(p);
end;

procedure CallbackProc(hDevice,Msg:DWORD;Com:TnrCommTAPI;Param1,Param2,Param3:DWORD);stdcall;
begin
//  OutputDebugString('call back');
  if Com<>nil then case Msg of

{    LINE_APPNEWCALL: begin
       //	if (stLineMsg.dwParam3==LINECALLPRIVILEGE_OWNER)
       Com.HandleConnection(Param2);
	//m_hCall = (HCALL) stLineMsg.dwParam2;
    end;  }
    LINE_REPLY:Com.HandleReply(Param1,Param2);
    LINE_CALLSTATE:begin
      Com.UpdateCallInfo(hDevice,LINECALLINFOSTATE_CALLERID or LINECALLINFOSTATE_CALLEDID);
      if Assigned(Com.OnCallState) then Com.OnCallState(Com,Param1,Param2);
      case Param1 of
        LINECALLSTATE_BUSY:
          if Assigned(Com.OnLineBusy) then Com.OnLineBusy(Com);
        LINECALLSTATE_PROCEEDING:
          Com.HandleProceeding;
        //LINECALLSTATE_IDLE: Com.HandleIDLE(Param2);
        LINECALLSTATE_DISCONNECTED: begin
            if Param2 = LINEDISCONNECTMODE_BUSY then begin
              if Assigned(Com.OnLineBusy) then Com.OnLineBusy(Com);
            end;
            Com.HandleDisconnection(hDevice,True);
          end;
        LINECALLSTATE_CONNECTED:
          Com.HandleConnection(hDevice);
        LINECALLSTATE_OFFERING:
          Com.HandleRing(hDevice);
      end;
    end;
    LINE_CALLINFO:begin
      Com.UpdateCallInfo(hDevice,Param1);
    end;

    LINE_MONITORDIGITS:if Assigned(Com.OnDTMF) then Com.OnDTMF(Com,Char(Param1));
    LINE_MONITORTONE:if Assigned(Com.OnTone) then Com.OnTone(Com);
  end;
end;

{ TnrCommTAPI }

constructor TnrCommTAPI.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  sLogPrefix:='nrCommTAPI: ';
  pcPhone:=StrAlloc(256);
  PauseTimer:=TnrTimer.Create;//(Self);
  PauseTimer.Enabled:=False;
  PauseTimer.Interval:=3000;
  PauseTimer.OnTimer:=DoPauseTimer;
  fIcon:=TIcon.Create;
  fTapiState:=ctsNeutral;
  fCallState:=tcsClosed;
  fRetries:=3;
  fModem:=TnrModemDevice.Create(Self);
  hLineApp:=0;
  fDeviceIndex := -1;
//  ReloadDeviceList;
//  fCallerID:='';
end;

destructor TnrCommTAPI.Destroy;
begin
  DataProcessor:=nil;
  FreeTAPI;
  DeviceListClear;
  fModem.Free;
  fIcon.Free;
  PauseTimer.Free;
  StrDispose(pcPhone);
//  fCallerID:='';
  if CallInfo<>nil then FreeMem(CallInfo);
  inherited;
end;

procedure TnrCommTAPI.DoFatalTAPIError(cd:integer;s: string);
begin
  DoFatalErrors(ECC_NRTAPI,cd,
  Format('nrCommLib TAPI error:'#13'%s'#13'TAPI Error Code [%Xh]'#13'%s',
    [s,LastTAPIResult,GetTapiErrorString(LastTAPIResult)]),False);
end;

procedure TnrCommTAPI.AllocTAPI;
var StrucInit:TLINEINITIALIZEEXPARAMS;
    ExtID:TLineExtensionID;
    ers:string;
    erc:integer;
begin
  APIVersion:=$20000;
  erc:=0;
  ZeroMemory(@StrucInit,SizeOf(TLINEINITIALIZEEXPARAMS));
  StrucInit.dwTotalSize:=sizeof(TLINEINITIALIZEEXPARAMS);
  StrucInit.dwOptions:=LINEINITIALIZEEXOPTION_USEHIDDENWINDOW;
  LastTAPIResult := lineInitialize(hLineApp,hInstance,@CallbackProc,nil,NumDevices);

//  StrucInit.dwOptions:=LINEINITIALIZEEXOPTION_USEEVENT;
//  LastTAPIResult := lineInitializeEx(hLineApp, hInstance, nil, nil, NumDevices, APIVersion, StrucInit);

  if LastTAPIResult<0 then begin
    hLineApp:=0;        // error of initialization
    ers:=nteInitTAPI;
    erc:=ntecInitTAPI;
  end else begin
    hEvent := StrucInit.Handles.hEvent;
    if NumDevices = 0 then begin
      lineShutdown(hLineApp); // error - no devices
      erc:=ntecNoDevices;
      ers:=nteNoDevices;
    end else begin
      LastTAPIResult:=lineNegotiateAPIVersion(hLineApp,0,$10000,$90000,APIVersion,ExtID);
      if LastTAPIResult<0 then begin
        lineShutdown(hLineApp);    // error invalid version
        erc:=ntecVerTAPI;
        ers:=nteVerTAPI;
      end;
    end;
  end;
  if hLineApp=0 then begin
    DoFatalTAPIError(erc,ers);
  end;
end;

procedure TnrCommTAPI.FreeTAPI;
begin
  CloseLine;
  if hLineApp<>0 then lineShutdown(hLineApp);
  dwInUse:=0;
end;

procedure TnrCommTAPI.NecessaryAllocTAPI;
begin
  if(dwInUse=0)and(hLineApp=0)then AllocTAPI;
  Inc(dwInUse);
end;

procedure TnrCommTAPI.NecessaryFreeTAPI;
begin
  Dec(dwInUse);
  if (dwInUse=0)and(hLineApp<>0) then FreeTAPI;
end;

{$IFDEF TRIAL}
var  ecx:integer = $5622FD60;
{$ENDIF}

procedure TnrCommTAPI.DeviceListLoad;
var ir:integer;
    mi:TModemInfo;
    Di:TnrDeviceTAPI;
//    infDevCaps:TLINEDEVCAPS;
begin
  DeviceListClear;
  NecessaryAllocTAPI;
  Logging(dlDebug, 'Enum TAPI ' + IntToStr(NumDevices));
  if hLineApp <> 0 then
  for ir := 0 to NumDevices - 1 do begin//try
    Logging(dlDebug, 'Enum TAPI try: ' + IntToStr(ir));
    LastTAPIResult := lineOpen(hLineApp, ir, hLine, APIVersion, 0, DWORD(Self), LINECALLPRIVILEGE_NONE, LINEMEDIAMODE_DATAMODEM, nil);
    if LastTAPIResult >= 0 then begin
      mi.vs.dwTotalSize := SizeOf(TModemInfo);
      mi.vs.dwStringFormat := STRINGFORMAT_BINARY;
      Logging(dlDebug, 'Enum TAPI try ok. getID now ...');
      LastTAPIResult := lineGetID(hLine,0,0,LINECALLSELECT_LINE,mi.vs,DeviceClassModems);
      if LastTAPIResult >= 0
      then begin
        Logging(dlDebug, 'Enum TAPI getID ok ');
        di := TnrDeviceTAPI.Create;
        di.DeviceName := String(AnsiString(GetModemCaption(mi))); //String(AnsiString(PAnsiChar(GetPCharFromVarString(mi.vs))));
        di.Handle:=mi.hComm;
        di.IndexTapi:=ir;
        //getting serial port name wich assigned to modem device ...
        if lineGetID(hLine,0,0,LINECALLSELECT_LINE,mi.vs,DeviceClassPort)<0
          then di.PortName:='???'
          else di.PortName := String(AnsiString(PAnsiChar(Cardinal(@mi.vs)+Cardinal(mi.vs.dwStringOffset))));

        //getting wave id for voice modem device ...
        di.WaveInId:=0;
        di.WaveOutId:=0;
        //checking full duplex ...
        if lineGetID(hLine,0,0,LINECALLSELECT_LINE,mi.vs,DeviceClassWaveInOut)>=0
          then begin
            di.WaveInId:=dword(Pointer(Cardinal(@mi.vs)+Cardinal(mi.vs.dwStringOffset))^);
            di.WaveOutId:=dword(Pointer(Cardinal(@mi.vs)+Cardinal(mi.vs.dwStringOffset)+4)^);
          end else begin
            if lineGetID(hLine,0,0,LINECALLSELECT_LINE,mi.vs,DeviceClassWaveOut)>=0
              then di.WaveOutId:=dword(Pointer(Cardinal(@mi.vs)+Cardinal(mi.vs.dwStringOffset))^);
            if lineGetID(hLine,0,0,LINECALLSELECT_LINE,mi.vs,DeviceClassWaveIn)>=0
              then di.WaveInId:=dword(Pointer(Cardinal(@mi.vs)+Cardinal(mi.vs.dwStringOffset))^);
          end;
        di.infDevCaps.dwTotalSize:=SizeOf(TLINEDEVCAPS);
        lineGetDevCaps(hLineApp,ir,APIVersion,0,@di.infDevCaps);
        di.Modes := di.infDevCaps.dwBearerModes;
        di.MediaModes := di.infDevCaps.dwMediaModes;
        di.NameFriendly := di.DeviceName;
        Logging(dlDebug, 'Enum TAPI add item: "' + di.NameFriendly  + '"');
        AddDevice(di);
        //Devices.AddObject(di^.DeviceName,TObject(di));
        CloseLine;
      end else begin
        hLine:=0;
        Logging(dlDebug, 'FAILED: ' + IntToHex(LastTAPIResult, 8));
      end;
    end else begin
      Logging(dlDebug, 'FAILED: ' + IntToHex(LastTAPIResult, 8));
    end;
  end;
  NecessaryFreeTAPI;
end;

procedure TnrCommTAPI.Close;
begin
  if Handle<>INVALID_HANDLE_VALUE then begin
    if fCurrentCallIsVoice
      then lineSetCallParams(hCall,LINEBEARERMODE_VOICE,0,0,nil);
    inherited Close;
  end;
end;

function TnrCommTAPI.RequestDeviceHandle: THandle;
var mi:TModemInfo;
begin
  Result := INVALID_HANDLE_VALUE;
  if(hLine<>0)then begin
    if fCurrentCallIsVoice
      then lineSetCallParams(hCall,LINEBEARERMODE_PASSTHROUGH,0,0,nil);
    mi.vs.dwTotalSize:=SizeOf(TModemInfo);
    mi.vs.dwStringFormat:=STRINGFORMAT_BINARY;
    //lineGetID(hLine,0,0,LINECALLSELECT_LINE,mi.vs,DeviceClassModems);
    //Result:=mi.hComm;
    if lineGetID(hLine,0,0,LINECALLSELECT_LINE{LINECALLSELECT_CALL},mi.vs,DeviceClassModems{'comm'})=0
      then Result:=PDword(Cardinal(@mi)+Cardinal(mi.vs.dwStringOffset))^;
  end;

  {$IFDEF TRIAL}
  if (ecx mod 7) = 0 then MessageBox(0, PChar(eax), nil, 0);
  {$ENDIF}

end;

(*function TnrCommTAPI.HandleForced: cardinal;
var mi:TModemInfo;
begin
  Result:=Cardinal(INVALID_HANDLE_VALUE);
  if(hLine<>0)and(hCall<>0)then begin

    lineSetCallParams(hCall,LINEBEARERMODE_PASSTHROUGH,0,0,nil);

    mi.vs.dwTotalSize:=SizeOf(TModemInfo);
    mi.vs.dwStringFormat:=STRINGFORMAT_BINARY;
    if lineGetID(hLine,0,0,LINECALLSELECT_LINE{LINECALLSELECT_CALL},mi.vs,DeviceClassModems{'comm'})=0
      then Result:=PCardinal(Cardinal(@mi)+Cardinal(mi.vs.dwStringOffset))^;
//    Result:=mi.hComm;
  end;
end;*)

procedure TnrCommTAPI.CloseLine;
begin
//  OutputDebugString(PChar('Begin close ...'+IntTOStr(integer(fCallState))));
//  if (fCallState=tcsClosing)or
//     (fCallState=tcsClosed) then exit;

  if (fCallState=tcsClosing) then exit;
  if (fCallState=tcsClosed) then begin
    if hLine<>0 then lineClose(hLine); //closing line
    hLine:=0;
    exit;
  end;

  fCallState:=tcsClosing;   // lock other line closing

//  if not Voice then Close   //closing com port handle for datamodems
//               else DoNotify(csClosed);
  Close;

  if hCall<>0 then begin     //if call then closing the call
    if not IsIDLE then begin
      ResetReply;
      LastTAPIResult:=lineDrop(hCall,nil,0);
      if LastTAPIResult>=0 then begin
        if WaitReply(LastTAPIResult,7000) = 0
          then WaitIDLE;
      end;
    end;
    lineDeallocateCall(hCall);
    hCall:=0;
    Logging(dlDetail,'Call closed');
  end else begin  // if uncomplete call cancels it
    if(fCallState=tcsAnswering)or(fCallState=tcsCalling)
      then begin
        ResetReply;
        LastTAPIResult:=lineUncompleteCall(hLine,0);
        if LastTAPIResult>=0
          then LastTAPIResult:=WaitReply(LastTAPIResult,7000);
      end;
  end;

  if hLine<>0 then begin
    lineClose(hLine); //closing line
    LoggingFmt(dlDetail,'Line closed [%s]',[GetDeviceName]);
  end;
  hLine:=0;
  fRingCount:=0;

  fTapiState:=ctsNeutral;
  fCallState:=tcsClosed;
//  OutputDebugString('... END close');
end;

function TnrCommTAPI.GetTrueDeviceIndex: integer;
begin
  if DeviceIndex >= 0
    then Result:= TnrDeviceTAPI(Device[DeviceIndex]).IndexTapi
    else Result:=-1;
end;

procedure TnrCommTAPI.Call;
var ir,sz:integer;
    s:string;
    TrOut:LPLINETRANSLATEOUTPUT;
begin
//  CloseLine;
  Logging(dlDetail,''); // add to log sys info ...
  FreeTAPI;
  DeviceModem.DeviceIndex := TrueDeviceIndex;
  if DeviceModem.DeviceIndex<0 then begin
    DoFatalTAPIError(ntecCall,
      Format('%s'#13+nteDeviceIndex,[nteCall,DeviceModem.DeviceIndex,DeviceCount]));
    exit;
  end;
  NecessaryAllocTAPI;
  DeviceModem.SaveProperties;

  if Voice then ir := LINEMEDIAMODE_AUTOMATEDVOICE //}LINEMEDIAMODE_INTERACTIVEVOICE
           else ir := LINEMEDIAMODE_DATAMODEM;

  //ir := LINEMEDIAMODE_UNKNOWN;

  fCurrentCallIsVoice:=Voice;
  LastTAPIResult := lineOpen(hLineApp, TrueDeviceIndex,hLine, APIVersion, 0, DWORD(Self),
                                  {LINECALLPRIVILEGE_OWNER}LINECALLPRIVILEGE_NONE,ir,nil);
  if LastTAPIResult >= 0 then begin

//    OutputDebugString(PChar('ls='+IntToHex(Device[DeviceIndex].infDevCaps.dwLineStates,8)));
//    if lineSetStatusMessages(hLine, Device[DeviceIndex].infDevCaps.dwLineStates,0) = 0
//      then OutputDebugString('OK');

    //TWaitThread.Create(Self);

    ZeroMemory(@LineParams,SizeOf(LineParams));

    LineParams.dwTotalSize := SizeOf(LineParams);

    if fCurrentCallIsVoice then begin
      LineParams.dwBearerMode := LINEBEARERMODE_VOICE; // }LINEBEARERMODE_VOICE;
      LineParams.dwMediaMode := LINEMEDIAMODE_AUTOMATEDVOICE; //}LINEMEDIAMODE_INTERACTIVEVOICE
    end else begin
      LineParams.dwBearerMode := LINEBEARERMODE_VOICE; // LINEBEARERMODE_DATA
      LineParams.dwMediaMode := LINEMEDIAMODE_DATAMODEM;
    end;

    LineParams.dwCallParamFlags := LINECALLPARAMFLAGS_IDLE;
    LineParams.dwAddressMode := LINEADDRESSMODE_DIALABLEADDR;// LINEADDRESSMODE_ADDRESSID;

    //LineParams.dwMinRate := 4800;
    //LineParams.dwMaxRate := 115200;

    fCurrentRetries:=0;
    s:=GetCanonical;
    sz:=SizeOf(TLINETRANSLATEOUTPUT)+TAPIMAXDESTADDRESSSIZE*2+2;
    GetMem(TrOut,sz);
    TrOut^.dwTotalSize:=sz;
    ResetReply;
    lineTranslateAddress(hLineApp,TrueDeviceIndex,APIVersion,PChar(s),0,0,TrOut);

    fDialableNumber:=String(PChar(Integer(TrOut)+TrOut^.dwDialableStringOffset));
    fDisplayableNumber:=String(PChar(Integer(TrOut)+TrOut^.dwDisplayableStringOffset));;
    lineGetAddressID(hLine,fAddressID,LINEADDRESSMODE_DIALABLEADDR,PChar(fDialableNumber),TrOut^.dwDialableStringSize);
    Logging(dlSimple,'Dial: '+Phone+' Canonical:'+s+' Dialable:'+fDialableNumber);

    LastTAPIResult := lineMakeCall(hLine, hCall, PChar(fDialableNumber), TrOut^.dwDestCountry, @LineParams);

    if LastTAPIResult < 0 then begin
      CloseLine;
    end else begin
       LastTAPIResult := WaitReply(LastTAPIResult,7000);
       if LastTAPIResult<>0 then begin
         CloseLine;
       end else begin
         fTapiState:=ctsCall;
         fCallState:=tcsCalling;
//         LastTAPIResult := lineMonitorMedia(hCall,LINEMEDIAMODE_AUTOMATEDVOICE or LINEMEDIAMODE_INTERACTIVEVOICE or LINEMEDIAMODE_UNKNOWN);
//         if LastTAPIResult = 0 then begin
//           OutputDebugString('OK2');
//         end else begin
//           OutputDebugString(PChar('Err2 ' + IntToHex(LastTAPIResult,8)));
//         end;

{         LastTAPIResult := lineMonitorDigits(hCall,LINEDIGITMODE_DTMF);
         if LastTAPIResult = 0 then begin
           OutputDebugString('OK3');
         end else begin
           OutputDebugString(PChar('Err2 ' + IntToHex(LastTAPIResult,8)));
         end; }

       end;
    end;
    if(hCall<>0)and(Assigned(Log))then Log.AddLogFmt(dlSimple,'Make Call Out : %s Device: %s',[fPhone,GetDeviceName]);
  end else hCall:=0;
  if hCall=0 then begin
    FreeTAPI;
    DoFatalTAPIError(ntecCall,nteCall);
  end;
end;

procedure TnrCommTAPI.WaitForIncomingCall;
var i:integer;
begin
//  CloseLine;
  Logging(dlDetail,''); // add to log sys info ...
  FreeTAPI;
  DeviceModem.DeviceIndex:=TrueDeviceIndex;
  if DeviceModem.DeviceIndex<0 then begin
    DoFatalTAPIError(ntecWaitCall,
      Format('%s'#13+nteDeviceIndex,[nteWaitCall,DeviceModem.DeviceIndex,DeviceCount]));
    exit;
  end;
  NecessaryAllocTAPI;
  if hLineApp=0 then exit;
  DeviceModem.SaveProperties;
  if Voice then i:=LINEMEDIAMODE_AUTOMATEDVOICE
           else i:=LINEMEDIAMODE_DATAMODEM;
  fCurrentCallIsVoice:=Voice;

  LastTAPIResult := lineOpen(hLineApp,TrueDeviceIndex,hLine,APIVersion,0,DWORD(Self),
                                 LINECALLPRIVILEGE_MONITOR or
                                 LINECALLPRIVILEGE_OWNER
                                 ,i,nil);
  if LastTAPIResult < 0 then begin
    hLine := 0;
    FreeTAPI;
    DoFatalTAPIError(ntecWaitCall,Format('%s',[nteWaitCall]));
  end else begin
    LoggingFmt(dlSimple,'Device [%s] wait for incoming call ...',[GetDeviceName]);
    fCurrentRetries := 0;
    fTapiState := ctsWait;
    fCallState := tcsWaiting;
  end;
end;

procedure TnrCommTAPI.HandleConnection(hInCall: Integer);
var dm:integer;
begin

  hCall := hInCall;

  if fCurrentCallIsVoice then begin
      if Pulse
        then dm := LINEDIGITMODE_PULSE
        else dm := LINEDIGITMODE_DTMF;
      lineMonitorDigits(hInCall,dm);
//         then OutputDebugString('set mon digit')
//         else OutputDebugString('set mon digit ERRR');
  end;// else OutputDebugString('NOT set mon digit');

  if (fCurrentCallIsVoice and (TraceStates<>[]))or
      (not fCurrentCallIsVoice)
    then Open;
//    else OutputDebugString('skip direct open');

(*  if not fCurrentCallIsVoice
    then Open  // if no voice open serial port
    else begin
      if Pulse
        then dm:=LINEDIGITMODE_PULSE
        else dm:=LINEDIGITMODE_DTMF;
      lineMonitorDigits(hInCall,dm);
      {Tn[0]:=1;
      Tn[1]:=250;
      Tn[2]:=4250;
      Tn[3]:=4800;
      Tn[4]:=6200;
      LastTAPIResult:=lineMonitorTones(hInCall,@Tn,1);
      if LastTAPIResult<>0 then begin
         DoFatalTAPIError(LastTAPIResult,' 55: ');
      end; }
      DoNotify(csOpened);
    end;
  *)

  fCallState := tcsConnected;
  {$IFDEF TRIAL}
  Inc(ecx);
  {$ENDIF}
  DeviceModem.DeviceIndex := TrueDeviceIndex;
  DeviceModem.LoadProperties;


  //UpdateCallInfo(hCall,0);

  LoggingFmt(dlSimple,'Connected [%u] hCom [%u] CallerID [%s]',
             [DeviceModem.BaudRateOfConnect,Handle,CallerID]);
  if Assigned(fOnLineConnected) then fOnLineConnected(Self);
end;

procedure TnrCommTAPI.HandleProceeding;
begin
  fCallState:=tcsConnecting;
  if Assigned(fOnLineDoingConnection) then fOnLineDoingConnection(Self);
end;

procedure TnrCommTAPI.HandleRing(hInCall:longint);
var flAnswerNow:boolean;
begin
  Logging(dlDebug,'Ring!');
  Inc(fRingCount);
  hCall:=0;

  {$IFDEF NR_WITH_CONTROLS}
  if Assigned(MonitorRing) then DoCheckMonitor(MonitorRing,True);
  {$ENDIF}
  if tsRing in TraceStates then begin
    if Assigned(fOnTraceState) then fOnTraceState(Self,[tsRing]);
  end;

  flAnswerNow := fRingCount >= fRingsBeforeAnswer;

  if Assigned(fOnRing) then fOnRing(Self,flAnswerNow);

  if not flAnswerNow {fRingCount<fRingsBeforeAnswer} then begin
    //not answering for this incoming call           LINECALLSTATE_CONNECTED
    lineDeallocateCall(hInCall);
  end else begin // Now answer ...

    //if fCurrentCallIsVoice
    //  then LastTAPIResult:=lineSetCallParams(hInCall,LINEBEARERMODE_PASSTHROUGH,0,0,nil)
    //  else LastTAPIResult:=lineAnswer(hInCall,nil,0);

    LastTAPIResult := lineAnswer(hInCall, nil, 0);

    if LastTAPIResult = Integer(LINEERR_NOTOWNER) then begin
      Logging(dlDebug, 'Enforce OWNER mode and answer again ...');
      // Enforce the owner privilege to the incoming call ...
      lineSetCallPrivilege(hInCall, LINECALLPRIVILEGE_OWNER);
      // .. and try answer again
      LastTAPIResult := lineAnswer(hInCall, nil, 0);
    end;

    if LastTAPIResult < 0 then begin
      DoFatalTAPIError(ntecAnswer, nteAnswer);
    end else begin
      LastTAPIResult := WaitReply(LastTAPIResult, 7000);
      if LastTAPIResult >= 0 then begin
        fCallState := tcsAnswering;
        Logging(dlSimple, 'Answer');
        hCall := hInCall;
      end;
    end;
 end;
end;

procedure TnrCommTAPI.HandleDisconnection(hInCall: Integer;ByRemote:boolean);
var tsSave:TTapiState;
begin
  if fCallState = tcsClosing then exit; // close is in progress
  Logging(dlSimple, 'Begin Handle Disconnection');
  hCall := hInCall;
  tsSave := fTapiState;
  CloseLine;
  Logging(dlSimple, 'Done Disconnection');
  if Assigned(fOnLineDisconnected) then fOnLineDisconnected(Self, True);
  if fRetries <> 0 then begin
    fTapiState := tsSave;
    Inc(fCurrentRetries);
    if (fRetries = -1) or (fCurrentRetries < fRetries)then begin
      PauseTimer.Enabled := True;
      if PauseTimer.Interval = 0 then DoPauseTimer(Self);
    end
  end;
end;

procedure TnrCommTAPI.HangUp;
begin
  if fCallState = tcsClosing then exit;
  Logging(dlDetail,'Begin HangUp');
//  CloseLine;
  FreeTAPI;
  Logging(dlSimple,'HangUp');
  if Assigned(fOnLineDisconnected) then fOnLineDisconnected(Self,False);
end;

procedure TnrCommTAPI.Answer;
var ir:integer;
    //s:string;
    //TrOut:LPLINETRANSLATEOUTPUT;
begin
  FreeTAPI;
  DeviceModem.DeviceIndex := TrueDeviceIndex;
  if DeviceModem.DeviceIndex < 0 then begin
    DoFatalTAPIError(ntecCall,
      Format('%s'#13+nteDeviceIndex,[nteCall,DeviceModem.DeviceIndex,DeviceCount]));
    exit;
  end;
  NecessaryAllocTAPI;
  DeviceModem.SaveProperties;
  if Voice then ir := LINEMEDIAMODE_AUTOMATEDVOICE //LINEMEDIAMODE_INTERACTIVEVOICE
           else ir := LINEMEDIAMODE_DATAMODEM;
  fCurrentCallIsVoice := Voice;
  LastTAPIResult := lineOpen(hLineApp,TrueDeviceIndex,hLine,APIVersion,0,DWORD(Self),
                                  LINECALLPRIVILEGE_NONE,ir,nil);
  if LastTAPIResult >= 0 then begin
    ZeroMemory(@LineParams,SizeOf(LineParams));
    LineParams.dwTotalSize := SizeOf(LineParams);
    if Voice then begin
      LineParams.dwBearerMode:=LINEBEARERMODE_VOICE;
      LineParams.dwMediaMode:=LINEMEDIAMODE_AUTOMATEDVOICE; //LINEMEDIAMODE_INTERACTIVEVOICE
    end else begin
      LineParams.dwBearerMode:=LINEBEARERMODE_VOICE;
      LineParams.dwMediaMode:=LINEMEDIAMODE_DATAMODEM;
    end;
    LineParams.dwCallParamFlags := LINECALLPARAMFLAGS_IDLE;
//    LineParams.dwAddressMode := LINEADDRESSMODE_DIALABLEADDR;// LINEADDRESSMODE_ADDRESSID;
    LineParams.dwAddressMode := LINEADDRESSMODE_ADDRESSID;
//    LineParams.dwAddressID = 0;
//    LineParams.DialParams.dwDialPause
    ResetReply;
    LastTAPIResult:=lineMakeCall(hLine,hCall,'',1,@LineParams);
    if LastTAPIResult<0 then begin
      CloseLine;
    end else begin
       LastTAPIResult:=WaitReply(LastTAPIResult,7000);
       if LastTAPIResult<>0 then begin
         CloseLine;
       end else begin
         //OutputDebugString('Answer!!!!');
         fTapiState:=ctsCall;
         fCallState:=tcsCalling;
//         HandleConnection(hCall);
       end;
    end;
    if(hCall<>0)and(Assigned(Log))then Log.AddLogFmt(dlSimple,'Make Call Out : %s Device: %s',[fPhone,GetDeviceName]);
  end else hCall:=0;
  if hCall=0 then begin
    FreeTAPI;
    DoFatalTAPIError(ntecCall,nteCall);
  end;
end;

function TnrCommTAPI.GetDeviceName: string;
begin
  if(fDeviceIndex >= 0) and (DeviceCount > 0) and ( fDeviceIndex < DeviceCount)
     then Result:=Device[fDeviceIndex].DeviceName
     else Result:='Error TAPI Device Name';
end;

{function TnrCommTAPI.GetDevice(Index: integer): string;
begin
  if(Index>=0)and(DeviceCount>0)and(Index<DeviceCount)
     then Result:=Devices[Index]
     else Result:='Error TAPI Device Name';
end;}

procedure TnrCommTAPI.DeviceConfigDialog;
begin
  NecessaryAllocTAPI;
  fModem.DeviceIndex := TrueDeviceIndex;
  fModem.SaveProperties;
  lineConfigDialog(TrueDeviceIndex, GetMainWindowHandle, DeviceClassModems);
  fModem.LoadProperties;
  NecessaryFreeTAPI;
end;

function TnrCommTAPI.GetPortName: string;
begin
  if(DeviceIndex < DeviceCount) and (DeviceIndex >= 0)
    then Result := Device[DeviceIndex].PortName
    else Result := '';
end;

function TnrCommTAPI.GetActive: boolean;
begin
  Result := (hLineApp <> 0) and ( hLine <> 0) and (hCall <> 0)
            and(Handle <> INVALID_HANDLE_VALUE);
end;

procedure TnrCommTAPI.SetActive(const Value: boolean);
begin
  if Value <> GetActive then begin
    if Value
      then Open
      else Close;
  end;
end;

procedure TnrCommTAPI.InitAfterOpen;
begin
  inherited InitAfterOpen;
//  DCBUpdate;
//  CommCfg.DCB.Flags:=CommCfg.DCB.Flags or $4000;
//  DCBApply;
end;

procedure TnrCommTAPI.LoadConfig(const Root: HKEY; const Key, Name: string;
                                 const flForCurrentDevice:boolean);
label fatal_exit,foo_exit;
var r:TRegistry;
    Buff:PChar;
    p:^TVARSTRING;
begin
  NecessaryAllocTAPI;
  if(DeviceIndex<0)or(DeviceIndex>DeviceCount)then begin
     DoFatalTAPIError(5,'Load config Error Device Not Found');
     goto foo_exit;
  end;
  r := TRegistry.Create;
  r.RootKey := Root;
  r.OpenKey(Key, True);
  if r.ValueExists(Name) then begin
    GetMem(Buff,2048);
    p:=@Buff[1];
    p^.dwTotalSize:=2048;
    p^.dwStringFormat:=STRINGFORMAT_BINARY;
    if lineGetDevConfig(TrueDeviceIndex,p^,DeviceClassModems)<0 then begin
       DoFatalTapiError(6,'Load config Error Device');
       goto fatal_exit;
    end;
    //if r.GetDataSize(Name)=(p^.dwNeededSize+1) then begin
      r.ReadBinaryData(Name,Buff^,p^.dwNeededSize+1);
      if not flForCurrentDevice then DeviceIndex:=Byte(Buff[0]);
      if lineSetDevConfig(TrueDeviceIndex,@Buff[1+SizeOf(TVARSTRING){p^.dwStringOffset}],p^.dwStringSize,DeviceClassModems)<0 then begin
        DoFatalTAPIError(7,'Load config Error Device');
      end;
    //end;
fatal_exit:
    FreeMem(Buff);
  end;
  r.Free;
foo_exit:
  NecessaryFreeTAPI;
end;

procedure TnrCommTAPI.SaveConfig(const Root: HKEY; const Key, Name: string);
label fatal_exit,foo_exit;
var r:TRegistry;
    Buff:PChar;
    p:^TVARSTRING;
begin
  NecessaryAllocTAPI;
  if(DeviceIndex<0)or(DeviceIndex>DeviceCount)then begin
     DoFatalTAPIError(8,'Save config Error Device');
     goto foo_exit;
  end;
  GetMem(Buff,2048);
  p:=@Buff[1];
  p^.dwTotalSize:=2048;
  p^.dwStringFormat:=STRINGFORMAT_BINARY;
  if lineGetDevConfig(TrueDeviceIndex,p^,DeviceClassModems)<0 then begin
     DoFatalTAPIError(9,'Save config Error Device');
     goto fatal_exit;
  end;
  Buff[0]:=Char(DeviceIndex);
  r:=TRegistry.Create;
  r.RootKey:=Root;
  r.OpenKey(Key,True);
  r.WriteBinaryData(Name,Buff^,p^.dwNeededSize+1);
  r.Free;
fatal_exit:
  FreeMem(Buff);
foo_exit:
  NecessaryFreeTAPI;
end;

function TnrCommTAPI.GetEventChar: char;
begin
  Result:=fEventChar;
end;

procedure TnrCommTAPI.SetEventChar(const Value: char);
begin
  if Active then begin
    inherited SetEventChar(Value);
    fEventChar := CHar(CommCfg.DCB.EvtChar);
  end else fEventChar:=Value;
end;

procedure TnrCommTAPI.DoPauseTimer(Sender: TObject);
var save:integer;
begin
  PauseTimer.Enabled:=False;
  save:=fCurrentRetries;
  case fTapiState of
    ctsCall:Call;
    ctsWait:WaitForIncomingCall;
  end;
  fCurrentRetries:=save;
end;

function TnrCommTAPI.GetPauseOnRepeat: integer;
begin
  Result:=PauseTimer.Interval;
end;

procedure TnrCommTAPI.SetPauseOnRepeat(const Value: integer);
begin
  PauseTimer.Interval:=Value;
end;

function TnrCommTAPI.GetIcon: TIcon;
var hi:HICON;
begin
  NecessaryAllocTAPI;
  fIcon.ReleaseHandle;

  lineGetIcon(TrueDeviceIndex,DeviceClassModems,hi);
  fIcon.Handle:=CopyIcon(hi);

  Result:=fIcon;
  FreeTAPI;
end;

procedure TnrCommTAPI.SetDeviceModem(const Value: TnrModemDevice);
begin
  fModem.Assign(Value);
end;

procedure TnrCommTAPI.SetDeviceIndex(const Value: integer);
begin
//  if (DeviceCount<=0) or (Value>=DeviceCount)
//    then ReloadDeviceList;

  if DeviceCount<=0 then fDeviceIndex := -1
  else begin
    if Value>=DeviceCount then fDeviceIndex:=DeviceCount-1
                          else fDeviceIndex := Value;
    NecessaryAllocTAPI;
    fModem.DeviceIndex:= GetTrueDeviceIndex;// Value;
    fModem.LoadProperties;
    NecessaryFreeTAPI;
  end;
  inherited;
end;

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

procedure TnrCommTAPI.ResetReply;
begin
  InterlockedExchange(flReply,0);
  InterlockedExchange(ReplyId,-1);
  InterlockedExchange(ReplyResult,0);
end;

procedure TnrCommTAPI.HandleReply(Id,Rslt:integer);
begin
  InterlockedExchange(ReplyId,Id);
  InterlockedExchange(ReplyResult,Rslt);
  InterlockedExchange(flReply,1);
end;

function TnrCommTAPI.WaitReply(Id:integer;TimeOut:integer): integer;
var i:cardinal;
begin
  i:=GetTickCount+Cardinal(TimeOut);
  while (flReply<>1)and(Id<>ReplyId) do begin
    {$IFDEF NR_WITH_CONTROLS}
    if Assigned(Application) then Application.ProcessMessages;
    {$ENDIF}
    Sleep(10);//
    if TimeOut<>-1 then
      if GetTickCount>=i then begin
        ReplyResult:=-1;
        break;
      end;
  end;
  Result:=ReplyResult;
end;

function TnrCommTAPI.IsIDLE: boolean;
var lcs:TLineCallStatus;
begin
  Result:=False; //if no line or is any errors return false.
  if hCall<>0 then begin
    ZeroMemory(@lcs,SizeOf(TLineCallStatus));
    lcs.dwTotalSize:=SizeOf(TLineCallStatus);
    if lineGetCallStatus(hCall,lcs)=0
      then Result:=((lcs.dwCallState and LINECALLSTATE_IDLE)<>0);
  end;
end;

procedure TnrCommTAPI.WaitIDLE;
begin
  if hCall<>0 then repeat
    {$IFDEF NR_WITH_CONTROLS}
    if Assigned(Application) then Application.ProcessMessages;
    {$ENDIF}
    Sleep(10);//
  until(hCall=0)or IsIDLE;
end;

function TnrCommTAPI.GetCanonical: string;
begin
  Result:='';
  if fCountryCode <> 0 then Result := Format('+%u ',[fCountryCode]);
  if (fCountryCode <> 0) and (fAreaCode <> '') then Result := Format('%s(%s) ',[Result,fAreaCode]);
  if Result = '' then begin
    if fPulse then Result := Result + 'p'
              else Result := Result + 't';
  end;
  Result := Result + fPhone;
end;

procedure TnrCommTAPI.NumberDialog;
var s:string;
begin
  s := GetCanonical;
  lineTranslateDialog(hLineApp, TrueDeviceIndex, APIVersion, GetMainWindowHandle, PChar(s));
end;

procedure TnrCommTAPI.SetVoice(Value: boolean);
begin
  if Value then begin
    fVoice := DeviceModem.VoiceOut and DeviceModem.VoiceIn;
    if not fVoice then DoFatalTAPIError(ntecVoiceFeature,nteVoiceFeature);
  end else fVoice := Value;
end;

procedure TnrCommTAPI.GenerateDigits(const Digits: string);
var dm:integer;
begin
  if hCall<>0 then begin
    if Pulse
        then dm:=LINEDIGITMODE_PULSE
        else dm:=LINEDIGITMODE_DTMF;
    lineGenerateDigits(hCall,dm,PChar(Digits),0);
  end;
end;

procedure TnrCommTAPI.SetAreaCode(const Value: string);
begin
  fAreaCode := Trim(Value);
end;

procedure TnrCommTAPI.GenerateTone;
var lt:TLINEGENERATETONE;
    p:pointer;
begin
  lt.dwFrequency:=425;
  lt.dwCadenceOn:=100;
  lt.dwCadenceOff:=0;
  lt.dwVolume:=$FFFF;
  p:=@lt;
  LastTAPIResult:=lineGenerateTone(hCall,LINETONEMODE_BUSY{LINETONEMODE_CUSTOM}, 0 {3000}, 1,@p);
  if LastTAPIResult<>0 then begin
    DoFatalTAPIError(LastTAPIResult,' 33: ');
  end;
end;

{function TnrCommTAPI.GetDeviceCount: integer;
begin
  Result:=Devices.Count;
end;             }

function TnrCommTAPI.GetPhone: string;
begin
  Result:=fPhone;
end;

function TnrCommTAPI.GetRetries: integer;
begin
  Result:=fRetries;
end;

function TnrCommTAPI.GetRingsBeforeAnswer: integer;
begin
  Result:=fRingsBeforeAnswer;
end;

function TnrCommTAPI.GetAreaCode: string;
begin
  Result:=fAreaCode;
end;

function TnrCommTAPI.GetCountryCode: integer;
begin
  Result := fCountryCode;
end;

function TnrCommTAPI.GetPulse: boolean;
begin
  Result := fPulse;
end;

function TnrCommTAPI.GetVoice: boolean;
begin
  Result:=fVoice;
end;

function TnrCommTAPI.GetOnCallState: TOnLineCallState;
begin
  Result := fOnCallState;
end;

function TnrCommTAPI.GetOnDTMF: TOnDTMFEvent;
begin
  Result := fOnDTMF;
end;

function TnrCommTAPI.GetOnLineBusy: TNotifyEvent;
begin
  Result := fOnLineBusy;
end;

function TnrCommTAPI.GetOnLineConnected: TNotifyEvent;
begin
  Result := fOnLineConnected;
end;

function TnrCommTAPI.GetOnLineDisconnected: TNotifyCallDisconnect;
begin
  Result := fOnLineDisconnected;
end;

function TnrCommTAPI.GetOnLineDoingConnection: TNotifyEvent;
begin
  Result := fOnLineDoingConnection;
end;

function TnrCommTAPI.GetOnLineError: TOnLineError;
begin
  Result := fOnLineError;
end;

function TnrCommTAPI.GetOnTone: TNotifyEvent;
begin
  Result := fOnTone;
end;

procedure TnrCommTAPI.SetCountryCode(const Value: integer);
begin
  fCountryCode:=Value;
end;

procedure TnrCommTAPI.SetOnCallState(const Value: TOnLineCallState);
begin
  fOnCallState:=Value;
end;

procedure TnrCommTAPI.SetOnDTMF(const Value: TOnDTMFEvent);
begin
  fOnDTMF:=Value;
end;

procedure TnrCommTAPI.SetOnLineBusy(const Value: TNotifyEvent);
begin
  fOnLineBusy:=Value;
end;

procedure TnrCommTAPI.SetOnLineConnected(const Value: TNotifyEvent);
begin
  fOnLineConnected:=Value;
end;

procedure TnrCommTAPI.SetOnLineDisconnected(
  const Value: TNotifyCallDisconnect);
begin
  fOnLineDisconnected:=Value;
end;

procedure TnrCommTAPI.SetOnLineDoingConnection(const Value: TNotifyEvent);
begin
  fOnLineDoingConnection:=Value;
end;

procedure TnrCommTAPI.SetOnLineError(const Value: TOnLineError);
begin
  fOnLineError:=Value;
end;

procedure TnrCommTAPI.SetOnTone(const Value: TNotifyEvent);
begin
  fOnTone:=Value;
end;

procedure TnrCommTAPI.SetPhone(const Value: string);
begin
  fPhone:=Value;
end;

procedure TnrCommTAPI.SetPulse(const Value: boolean);
begin
  fPulse:=Value;
end;

procedure TnrCommTAPI.SetRetries(const Value: integer);
begin
  fRetries:=Value;
end;

procedure TnrCommTAPI.SetRingsBeforeAnswer(const Value: integer);
begin
  fRingsBeforeAnswer:=Value;
end;

{procedure TnrCommTAPI.ClearDeviceList;
var i:integer;
begin
  for i:=0 to Devices.Count-1 do
    Dispose(PnrDeviceInfo(Devices.Objects[i]));
  Devices.Clear;
end;                 }

{function TnrCommTAPI.GetCallerID: string;
begin
  Result:='';
  if (CallInfo<>nil)and(hCall<>0) then
    if (CallInfo^.dwCallerIDSize>0)and(CallInfo^.dwCallerIDOffset>0)then begin
      SetLength(Result,CallInfo^.dwCallerIDSize);
      CopyMemory(@Result[1],@(PChar(CallInfo)[CallInfo^.dwCallerIDOffset]),CallInfo^.dwCallerIDSize);
  end;
end;}

procedure TnrCommTAPI.UpdateCallInfo(const hACall,dwStatus: cardinal);
var sz:cardinal;
    rslt:cardinal;
begin
  if CallInfo<>nil then FreeMem(CallInfo);
  sz:=SizeOf(TLineCallInfo)+2048;
  GetMem(CallInfo,sz);
  CallInfo^.dwTotalSize:=sz;
  rslt:=lineGetCallInfo(hACall,CallInfo);
  if rslt=Cardinal(LINEERR_STRUCTURETOOSMALL) then begin
    sz:=CallInfo^.dwNeededSize;
    FreeMem(CallInfo);
    GetMem(CallInfo,sz);
    CallInfo^.dwTotalSize:=sz;
    rslt:=lineGetCallInfo(hACall,CallInfo);
  end;

  if rslt<>0 then begin
    FreeMem(CallInfo);
    CallInfo:=nil;
    exit;
  end;

  if (LINECALLINFOSTATE_CALLERID and dwStatus) <> 0 then begin
    if (CallInfo^.dwCallerIDSize>0)and(CallInfo^.dwCallerIDOffset>0)then begin

      SetLength(fCallerID,CallInfo^.dwCallerIDSize);
      CopyMemory(@fCallerID[1],@(PChar(CallInfo)[CallInfo^.dwCallerIDOffset]),CallInfo^.dwCallerIDSize);
    end;

    if (CallInfo^.dwCallerIDNameSize>0)and(CallInfo^.dwCallerIDNameOffset>0)then begin
       SetLength(fCallerIDName,CallInfo^.dwCallerIDNameSize);
       CopyMemory(@fCallerIDName[1],@(PChar(CallInfo)[CallInfo^.dwCallerIDNameOffset]),CallInfo^.dwCallerIDNameSize);
    end;
  end;
  // else
  if (LINECALLINFOSTATE_CALLEDID and dwStatus) <> 0 then begin
    if (CallInfo^.dwCalledIDSize>0)and(CallInfo^.dwCalledIDOffset>0)then begin
      SetLength(fCalledID,CallInfo^.dwCalledIDSize);
      CopyMemory(@fCalledID[1],@(PChar(CallInfo)[CallInfo^.dwCalledIDOffset]),CallInfo^.dwCalledIDSize);
    end;

    if (CallInfo^.dwCalledIDNameSize>0)and(CallInfo^.dwCalledIDNameOffset>0)then begin
      SetLength(fCalledIDName,CallInfo^.dwCalledIDSize);
      CopyMemory(@fCalledIDName[1],@(PChar(CallInfo)[CallInfo^.dwCalledIDNameOffset]),CallInfo^.dwCalledIDNameSize);
    end;

  end;

end;

{procedure TnrCommTAPI.SendString(const S: string);
var hCom:cardinal;
begin
//  inherited SendString(s)
  if not Voice
    then inherited SendString(s)
    else begin
      hCom:=HandleForced;
      if hCom<>INVALID_HANDLE_VALUE
        then WriteSync(hCom,@s[1],Length(s));
      lineSetCallParams(hCall,LINEBEARERMODE_VOICE,0,0,nil);
      CloseHandle(hCom);
    end;
end;}

procedure TnrCommTAPI.Update;
begin
//  ReloadDeviceList;
  DeviceListLoad;
  inherited Update;
end;

function TnrCommTAPI.GetDeviceTAPI(Index: integer): TnrDeviceTAPI;
begin
  Result := TnrDeviceTAPI(inherited Device[Index]);
end;

{ TnrModemDevice }

constructor TnrModemDevice.Create(AOwner:TnrCommTAPI);
begin
  inherited Create;
  GetMem(wavInfOut,SizeOf(WAVEOUTCAPS));
  GetMem(wavInfIn,SizeOf(WAVEINCAPS));
  nrTAPI:=AOwner;
  GetMem(DevCfg,2048);
  DeviceIndex:=-1;
  fInactivityTimeout:=10;
  fCallFailTimer:=60;
  fFlowControl:=spXonXoff;
  fSpeakMode:=smDial;
  fSpeakVol:=svLow;
  fBaudRateMax:=115200;
  fOptions:=[moShowIcon,moErrorControl,moForcedConnection,moCompression];
end;

destructor TnrModemDevice.Destroy;
begin
  FreeMem(DevCfg);
  FreeMem(wavInfOut);
  FreeMem(wavInfIn);
  inherited;
end;

procedure TnrModemDevice.LoadProperties;
begin
  //nrTAPI.NecessaryAllocTAPI;
  if(DeviceIndex>=0)and(nrTAPI.hLineApp<>0)then begin
    DevCfg^.vs.dwTotalSize:=2048;
    DevCfg^.vs.dwStringFormat:=STRINGFORMAT_BINARY;
    if lineGetDevConfig(DeviceIndex,DevCfg^.vs,DeviceClassModems)=0 then
      if DevCfg^.CommConfig.dwProviderSubType=PST_MODEM then
        with DevCfg^.CommConfig do begin
          fInactivityTimeout:=ModemSettings.dwInactivityTimeout;
          fCallFailTimer:=ModemSettings.dwCallSetupFailTimer;
          fSpeakMode:=TSpeakMode(ModemSettings.dwSpeakerMode);
          fSpeakVol:=TSpeakVol(ModemSettings.dwSpeakerVolume);
          fBaudRateOfConnection:=ModemSettings.dwNegotiatedDCERate;
          fBaudRateMax:=dcb.BaudRate;
          fOptions:=[];
          fOptionsOfConnect:=[];

          with ModemSettings do begin

            //preffered options ...
            if(dwPreferredModemOptions and MDM_FLOWCONTROL_SOFT)<>0
              then fFlowControl:=spXonXoff
            else
            if(dwPreferredModemOptions and MDM_FLOWCONTROL_HARD)<>0
              then fFlowControl:=spHardware
            else
              fFlowControl:=spNone;

            if(dwPreferredModemOptions and MDM_ERROR_CONTROL)<>0
              then fOptions:=fOptions+[moErrorControl];
            if(dwPreferredModemOptions and MDM_COMPRESSION)<>0
              then  fOptions:= fOptions+ [moCompression];
            if (dwPreferredModemOptions and MDM_FORCED_EC)<>0
              then  fOptions := fOptions + [moForcedConnection];
            if (dwPreferredModemOptions and MDM_BLIND_DIAL)=0
              then  fOptions := fOptions + [moWaitTone];
            //options of current conenction ...
            if(dwNegotiatedModemOptions and MDM_FLOWCONTROL_SOFT)<>0
              then fFlowControlOfConnect:=spXonXoff
            else
            if(dwNegotiatedModemOptions and MDM_FLOWCONTROL_HARD)<>0
              then fFlowControlOfConnect:=spHardware
            else
              fFlowControlOfConnect:=spNone;

            if(dwNegotiatedModemOptions and MDM_ERROR_CONTROL)<>0
              then fOptionsOfConnect:=fOptionsOfConnect+[moErrorControl];
            if(dwNegotiatedModemOptions and MDM_COMPRESSION)<>0
              then  fOptionsOfConnect:= fOptionsOfConnect+ [moCompression];
            if (dwNegotiatedModemOptions and MDM_FORCED_EC)<>0
              then  fOptionsOfConnect := fOptionsOfConnect + [moForcedConnection];
          end;

          if (DevCfg^.dfgHdr.fwOptions and 1)<>0 then fOptions:=fOptions+[moPreTerminal];
          if (DevCfg^.dfgHdr.fwOptions and 2)<>0 then fOptions:=fOptions+[moPostTerminal];
          if (DevCfg^.dfgHdr.fwOptions and 8)<>0 then fOptions:=fOptions+[moShowIcon];

        end;
  end;
  //nrTAPI.NecessaryFreeTAPI;
end;

procedure TnrModemDevice.SaveProperties;
begin
  //nrTAPI.NecessaryAllocTAPI;
  if(DeviceIndex>=0)and(nrTAPI.hLineApp<>0)then begin
    DevCfg^.vs.dwTotalSize:=2048;
    DevCfg^.vs.dwStringFormat:=STRINGFORMAT_BINARY;
    if lineGetDevConfig(DeviceIndex,DevCfg^.vs,DeviceClassModems)=0 then
      if DevCfg^.CommConfig.dwProviderSubType=PST_MODEM then
        with DevCfg^.CommConfig do begin
          ModemSettings.dwInactivityTimeout:=fInactivityTimeout;
          ModemSettings.dwCallSetupFailTimer:=fCallFailTimer;
          ModemSettings.dwSpeakerMode:=Cardinal(fSpeakMode);
          ModemSettings.dwSpeakerVolume:=Cardinal(fSpeakVol);
          dcb.BaudRate:=ModemSettings.dwDevSpecificOffset;
          dcb.BaudRate:=fBaudRateMax;
          with ModemSettings do begin
            dcb.Flags:=dcb.Flags and $FFFFCCFB;
            dwPreferredModemOptions:=dwPreferredModemOptions and (not(MDM_FLOWCONTROL_SOFT or MDM_FLOWCONTROL_HARD or
               MDM_ERROR_CONTROL or MDM_SPEED_ADJUST or MDM_COMPRESSION  or MDM_FORCED_EC));
            if fFlowControl = spXonXoff then begin
              dcb.Flags:=dcb.Flags or $300;
              dwPreferredModemOptions := dwPreferredModemOptions or MDM_FLOWCONTROL_SOFT;
            end else if fFlowControl = spHardware then begin
              dcb.Flags:=dcb.Flags or $2004;
              dwPreferredModemOptions := dwPreferredModemOptions or MDM_FLOWCONTROL_HARD;
            end;

            if moErrorControl in fOptions then
              dwPreferredModemOptions := dwPreferredModemOptions or MDM_ERROR_CONTROL or MDM_SPEED_ADJUST;
            if moCompression in fOptions then
              dwPreferredModemOptions := dwPreferredModemOptions or MDM_COMPRESSION or MDM_SPEED_ADJUST;
            if moForcedConnection in fOptions then
              dwPreferredModemOptions := dwPreferredModemOptions or MDM_FORCED_EC or MDM_SPEED_ADJUST;
            if not(moWaitTone in fOptions) then
              dwPreferredModemOptions := dwPreferredModemOptions or MDM_BLIND_DIAL;

          end;
{MDM_BLIND_DIAL MDM_CCITT_OVERRIDE MDM_CELLULAR MDM_SPEED_ADJUST MDM_TONE_DIAL
MDM_V23_OVERRIDE }
          DevCfg^.dfgHdr.fwOptions:=0;
          if moPreTerminal in fOptions then Inc(DevCfg^.dfgHdr.fwOptions);
          if moPostTerminal in fOptions then DevCfg^.dfgHdr.fwOptions:=DevCfg^.dfgHdr.fwOptions or 2;
          if moShowIcon in fOptions then DevCfg^.dfgHdr.fwOptions:=DevCfg^.dfgHdr.fwOptions or 8;
        end;
    lineSetDevConfig(DeviceIndex,@DevCfg^.dfgHdr,DevCfg^.vs.dwStringSize,DeviceClassModems);
  end;
end;

procedure TnrModemDevice.Assign(Source: TPersistent);
begin
  inherited;
  if(Source<>Self)and(Source<>nil)then begin
    fInactivityTimeout:=TnrModemDevice(Source).InactivityTimeout;
    fCallFailTimer:=TnrModemDevice(Source).CallFailTimer;
    fSpeakMode:=TnrModemDevice(Source).SpeakerMode;
    fSpeakVol:=TnrModemDevice(Source).SpeakerVol;
    fOptions:=TnrModemDevice(Source).Options;
    fFlowControl:=TnrModemDevice(Source).FlowControl;
    SaveProperties;
  end;             
end;

function TnrModemDevice.GetSpeakerMode: TSpeakMode;
begin
  LoadProperties;
  Result:=fSpeakMode;
end;

procedure TnrModemDevice.SetSpeakerMode(const Value: TSpeakMode);
begin
  fSpeakMode:=Value;
  SaveProperties;
end;

function TnrModemDevice.GetSpeakVol: TSpeakVol;
begin
  LoadProperties;
  Result:=fSpeakVol;
end;

procedure TnrModemDevice.SetSpeakVol(const Value: TSpeakVol);
begin
  fSpeakVol:=Value;
  SaveProperties;
end;

function TnrModemDevice.GetTimeSetting(Index:integer): cardinal;
begin
  LoadProperties;
  case Index of
    0:Result:=fInactivityTimeout;
    1:Result:=fCallFailTimer;
    2:Result:=fBaudRateOfConnection;
    else Result:=fBaudRateMax;
  end;
end;

procedure TnrModemDevice.SetTimeSetting(const Index: Integer;
  const Value: cardinal);
begin
  case Index of
    0:fInactivityTimeout:=Value;
    1:fCallFailTimer:=Value;
    3:fBaudRateMax:=Value;
  end;
  SaveProperties;
end;

function TnrModemDevice.GetOptions(Index:integer): TModemOptions;
begin
  LoadProperties;
  if Index=0 then Result:=fOptions
             else Result:=fOptionsOfConnect;
end;

procedure TnrModemDevice.SetOptions(const Index: Integer; const Value: TModemOptions);
begin
  fOptions:=Value;
  SaveProperties;
end;

function TnrModemDevice.GetFlowControl(Index:integer): TStreamProtocol;
begin
  LoadProperties;
  if Index=0 then Result:=fFlowControl
             else Result:=fFlowControlOfConnect;
end;

procedure TnrModemDevice.SetFlowControl(const Index: Integer; const Value: TStreamProtocol);
begin
  fFlowControl:=Value;
  SaveProperties;
end;

function TnrModemDevice.GetVoice(const Index:integer): boolean;
//var di:PnrDeviceInfo;
begin
//  di:=Pointer(nrTAPI.Devices.Objects[nrTAPI.DeviceIndex]);
  if Index = 0
    then Result:=(nrTAPI.Device[nrTAPI.DeviceIndex].WaveOutId<>0)
    else Result:=(nrTAPI.Device[nrTAPI.DeviceIndex].WaveInId<>0);
end;

function TnrModemDevice.GetVoiceID(const Index: Integer): integer;
//var di:PnrDeviceInfo;
begin
//  di:=Pointer(nrTAPI.Devices.Objects[nrTAPI.DeviceIndex]);
  Result := -1;
  if nrTAPI.DeviceIndex < 0 then exit;
  if Index = 0
    then Result:=(nrTAPI.Device[nrTAPI.DeviceIndex].WaveOutId)
    else Result:=(nrTAPI.Device[nrTAPI.DeviceIndex].WaveInId);
end;

{$IFDEF TRIAL}
function eax:string;
Const
  //You are using nrComm Lib trial version!
  //Please register! xor t
  esi:PChar=
 #$2D#$1B#$01#$54#$15#$06#$11#$54#$01#$07#$1D#$1A#$13#$54#$00#$06 +
 #$1D#$15#$18#$54#$1A#$06#$37#$1B#$19#$19#$38#$1D#$16#$54#$02#$11 +
 #$06#$07#$1D#$1B#$1A#$55#$79#$7E#$24#$18#$11#$15#$07#$11#$54#$06 +
 #$11#$13#$1D#$07#$00#$11#$06#$55;

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

function TnrModemDevice.GetVoiceFormats(const Index:integer): TVoiceFormats;
var di:TnrDeviceTAPI;
begin
  Result:=[];
  di:=nrTAPI.Device[nrTAPI.DeviceIndex];
  if Index=0 then
    if di.WaveOutId<>0 then begin
      waveOutGetDevCaps(di.WaveOutId,wavInfOut,SizeOf(WAVEOUTCAPS));
      Result:=TVoiceFormats(dword(wavInfOut^.dwFormats));
    end;
  if Index=1 then
    if di.WaveInId<>0 then begin
      waveInGetDevCaps(di.WaveInId,wavInfIn,SizeOf(WAVEINCAPS));
      Result:=TVoiceFormats(dWord(wavInfIn^.dwFormats));
    end;
end;

function TnrModemDevice.GetInitString: string;
var hK:HKEY;
    s:PChar;
    n:integer;
begin
  Result:='';
  if sRegKey='' then exit;
  if RegOpenKeyEx(HKEY_LOCAL_MACHINE,PChar(sRegKey),0,KEY_QUERY_VALUE,hK)=ERROR_SUCCESS
  then begin
    n:=4096;
    GetMem(s,n);
    if RegQueryValueEx(hK,'UserInit',nil,nil,PByte(s),@n)=ERROR_SUCCESS
      then Result:=String(s);
    FreeMem(s);  
    RegCloseKey(hK);
  end;
end;

procedure TnrModemDevice.SetInitString(const Value: string);
var hK:HKEY;
    n:integer;
begin
  if sRegKey='' then exit;
  if RegOpenKeyEx(HKEY_LOCAL_MACHINE,PChar(sRegKey),0,KEY_WRITE,hK)=ERROR_SUCCESS
  then begin
    n:=Length(Value)+1;
    RegSetValueEx(hK,'UserInit',0,REG_SZ,@Value[1],n);
    RegCloseKey(hK);
  end;
end;

procedure TnrModemDevice.SetDeviceIndex(const Value: integer);
var sN:string;
begin
  if fDeviceIndex = Value then exit;
  fDeviceIndex := Value;
  sRegKey:='';
  if Value<0 then exit;
  sN:=nrTAPI.Device[nrTAPI.DeviceIndex].DeviceName;
  sRegKey:=GetRegByModemName(sN);
end;

{ TnrCustomTAPIProcessor }

function TnrCustomTAPIProcessor.GetCommTAPI: TnrCommTAPI;
begin
  Result:=TnrCommTAPI(Device);
end;

procedure TnrCustomTAPIProcessor.SetCommTAPI(const Value: TnrCommTAPI);
begin
  Device:=TnrBaseIO(Value);
end;

{ TWaitThread }
(*
constructor TWaitThread.Create(aOwner: TnrCommTAPI);
begin
  inherited Create(True);
  FreeOnTerminate := True;
  nrTapi := aOwner;
  Priority := tpHighest;
  Resume;
end;

procedure TWaitThread.Execute;
var r:dword;
    Msg:TLineMessage;
begin
  OutputDebugString('Thread start ...');
  while not Terminated do begin
    r := WaitForSingleObject(nrTapi.hEvent,INFINITE);
    if r = WAIT_OBJECT_0 then begin
      OutputDebugString('pulse');
      if Terminated then break;
      r := lineGetMessage(nrTapi.hLineApp,Msg,0);
      if r = LINEERR_INVALAPPHANDLE then break;
      if r = 0 then CallbackProc(Msg.hDevice,Msg.dwMessageID,nrTapi,
                                 Msg.dwParam1, Msg.dwParam2, Msg.dwParam3);
    end else break;
  end;
  OutputDebugString('... thread exit');
end;

*)

initialization

finalization
  ModemList.Free;
  {$IFDEF TRIAL}
  if (ecx mod 3) = 0 then MessageBox(0, PChar(eax), nil, 0);
  {$ENDIF}

end.
