ATSP.C

/*++ 

Copyright 1995 - 1998 Microsoft Corporation

Module Name:

atsp.c

Notes:

--*/


#include "atsp.h"


BOOL
WINAPI
DllMain(
HANDLE hDLL,
DWORD dwReason,
LPVOID lpReserved
)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
ghInst = hDLL;

#if DBG
{
HKEY hKey;
DWORD dwDataSize, dwDataType;
char szAtsp32DebugLevel[] = "Atsp32DebugLevel";


RegOpenKeyExA(
HKEY_LOCAL_MACHINE,
gszAtspKey,
0,
KEY_ALL_ACCESS,
&hKey
);

dwDataSize = sizeof (DWORD);
gdwDebugLevel=0;

RegQueryValueEx(
hKey,
szAtsp32DebugLevel,
0,
&dwDataType,
(LPBYTE) &gdwDebugLevel,
&dwDataSize
);

RegCloseKey (hKey);
}
#endif

}

return TRUE;
}


void
CommThread(
PDRVLINE pLine
)
{
char buf[4];
DWORD dwThreadID = GetCurrentThreadId(), dwNumBytes;
HANDLE hComm = pLine->hComm, hEvent;
LPOVERLAPPED pOverlapped = &pLine->Overlapped;


DBGOUT((
3,
"CommThread (id=%d): enter, port=%s",
dwThreadID,
pLine->szComm
));

hEvent = pOverlapped->hEvent;
buf[0] = buf[1] = '.';


//
// Loop waiting for i/o to complete (either the Write done in
// TSPI_lineMakeCall or the Reads done to retrieve status info).
// Note that TSPI_lineDrop or TSPI_lineCloseCall may set the
// event to alert us that they're tearing down the call, in
// which case we just exit.
//

for (;;)
{
if (WaitForSingleObject (hEvent, ATSP_TIMEOUT) == WAIT_OBJECT_0)
{
if (pLine->bDropInProgress == TRUE)
{
DBGOUT((2, "CommThread (id=%d): drop in progress"));
goto CommThread_exit;
}

GetOverlappedResult (hComm, pOverlapped, &dwNumBytes, FALSE);
ResetEvent (hEvent);
}
else
{
DBGOUT((2, "CommThread (id=%d): wait timeout"));
SetCallState (pLine, LINECALLSTATE_IDLE, 0);
goto CommThread_exit;
}

buf[1] &= 0x7f; // nuke the parity bit

DBGOUT((
3,
"CommThread (id=%d): read '%c'",
dwThreadID,
(buf[1] == '\r' ? '.' : buf[1])
));

switch ((buf[0] << 8) + buf[1])
{
case 'CT': // "CONNECT"
case 'OK': // "OK"

SetCallState (pLine, LINECALLSTATE_CONNECTED, 0);
goto CommThread_exit;

case 'SY': // "BUSY"
case 'OR': // "ERROR"
case 'NO': // "NO ANSWER", "NO DIALTONE", "NO CARRIER"

SetCallState (pLine, LINECALLSTATE_IDLE, 0);
goto CommThread_exit;

default:

break;
}

buf[0] = buf[1];

ZeroMemory (pOverlapped, sizeof (OVERLAPPED) - sizeof (HANDLE));
ReadFile (hComm, &buf[1], 1, &dwNumBytes, pOverlapped);
}

CommThread_exit:

CloseHandle (hEvent);
DBGOUT((3, "CommThread (id=%d): exit", dwThreadID));
ExitThread (0);
}


//
// We get a slough of C4047 (different levels of indrection) warnings down
// below in the initialization of FUNC_PARAM structs as a result of the
// real func prototypes having params that are types other than DWORDs,
// so since these are known non-interesting warnings just turn them off
//

#pragma warning (disable:4047)


//
// --------------------------- TAPI_lineXxx funcs -----------------------------
//

LONG
TSPIAPI
TSPI_lineClose(
HDRVLINE hdLine
)
{
LONG lResult = 0;
#if DBG
FUNC_PARAM params[] =
{
{ gszhdLine, hdLine }
};
FUNC_INFO info =
{
"TSPI_lineClose",
1,
params,
};
#endif

Prolog (&info);
DrvFree ((PDRVLINE) hdLine);
return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineCloseCall(
HDRVCALL hdCall
)
{
PDRVLINE pLine = (PDRVLINE) hdCall;
#if DBG
FUNC_PARAM params[] =
{
{ gszhdCall, hdCall }
};
FUNC_INFO info =
{
"TSPI_lineCloseCall",
1,
params
};
#endif


//
// Note that in TAPI 2.0 TSPI_lineCloseCall can get called
// without TSPI_lineDrop ever being called, so we need to
// be prepared for either case.
//

Prolog (&info);
DropActiveCall (pLine);
pLine->htCall = NULL;
return (Epilog (&info, 0));
}


LONG
TSPIAPI
TSPI_lineConditionalMediaDetection(
HDRVLINE hdLine,
DWORD dwMediaModes,
LPLINECALLPARAMS const lpCallParams
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszhdLine, hdLine },
{ "dwMediaModes", dwMediaModes },
{ gszlpCallParams, lpCallParams }
};
FUNC_INFO info =
{
"TSPI_lineConditionalMediaDetection",
3,
params
};
#endif


//
// This func is really a no-op for us, since we don't look
// for incoming calls (though we do say we support them to
// make apps happy)
//

Prolog (&info);
return (Epilog (&info, 0));
}


LONG
TSPIAPI
TSPI_lineDrop(
DRV_REQUESTID dwRequestID,
HDRVCALL hdCall,
LPCSTR lpsUserUserInfo,
DWORD dwSize
)
{
PDRVLINE pLine = (PDRVLINE) hdCall;
#if DBG
FUNC_PARAM params[] =
{
{ gszdwRequestID, dwRequestID },
{ gszhdCall, hdCall },
{ "lpsUserUserInfo", lpsUserUserInfo },
{ gszdwSize, dwSize }
};
FUNC_INFO info =
{
"TSPI_lineDrop",
4,
params
};
#endif


Prolog (&info);
DropActiveCall (pLine);
SetCallState (pLine, LINECALLSTATE_IDLE, 0);
(*gpfnCompletionProc)(dwRequestID, 0);
return (Epilog (&info, dwRequestID));
}


LONG
TSPIAPI
TSPI_lineGetAddressCaps(
DWORD dwDeviceID,
DWORD dwAddressID,
DWORD dwTSPIVersion,
DWORD dwExtVersion,
LPLINEADDRESSCAPS lpAddressCaps
)
{

#if DBG
FUNC_PARAM params[] =
{
{ gszdwDeviceID, dwDeviceID },
{ "dwAddressID", dwAddressID },
{ "dwTSPIVersion", dwTSPIVersion },
{ "dwExtVersion", dwExtVersion },
{ "lpAddressCaps", lpAddressCaps }
};
FUNC_INFO info =
{
"TSPI_lineGetAddressCaps",
5,
params
};
#endif

LONG lResult = 0;


Prolog (&info);

if (dwAddressID != 0)
{
lResult = LINEERR_INVALADDRESSID;
}

lpAddressCaps->dwNeededSize =
lpAddressCaps->dwUsedSize = sizeof(LINEADDRESSCAPS);

lpAddressCaps->dwLineDeviceID = dwDeviceID;
lpAddressCaps->dwAddressSharing = LINEADDRESSSHARING_PRIVATE;
lpAddressCaps->dwCallInfoStates = LINECALLINFOSTATE_MEDIAMODE |
LINECALLINFOSTATE_APPSPECIFIC;
lpAddressCaps->dwCallerIDFlags =
lpAddressCaps->dwCalledIDFlags =
lpAddressCaps->dwRedirectionIDFlags =
lpAddressCaps->dwRedirectingIDFlags = LINECALLPARTYID_UNAVAIL;
lpAddressCaps->dwCallStates = LINECALLSTATE_IDLE |
LINECALLSTATE_OFFERING |
LINECALLSTATE_ACCEPTED |
LINECALLSTATE_DIALTONE |
LINECALLSTATE_DIALING |
LINECALLSTATE_CONNECTED |
LINECALLSTATE_PROCEEDING |
LINECALLSTATE_DISCONNECTED |
LINECALLSTATE_UNKNOWN;
lpAddressCaps->dwDialToneModes = LINEDIALTONEMODE_UNAVAIL;
lpAddressCaps->dwBusyModes = LINEBUSYMODE_UNAVAIL;
lpAddressCaps->dwSpecialInfo = LINESPECIALINFO_UNAVAIL;
lpAddressCaps->dwDisconnectModes = LINEDISCONNECTMODE_NORMAL |
LINEDISCONNECTMODE_BUSY |
LINEDISCONNECTMODE_NOANSWER |
LINEDISCONNECTMODE_UNAVAIL |
LINEDISCONNECTMODE_NODIALTONE;
lpAddressCaps->dwMaxNumActiveCalls = 1;
lpAddressCaps->dwAddrCapFlags = LINEADDRCAPFLAGS_DIALED;
lpAddressCaps->dwCallFeatures = LINECALLFEATURE_ACCEPT |
LINECALLFEATURE_ANSWER |
LINECALLFEATURE_DROP |
LINECALLFEATURE_SETCALLPARAMS;
lpAddressCaps->dwAddressFeatures = LINEADDRFEATURE_MAKECALL;

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineGetAddressStatus(
HDRVLINE hdLine,
DWORD dwAddressID,
LPLINEADDRESSSTATUS lpAddressStatus
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszhdLine, hdLine },
{ "dwAddressID", dwAddressID },
{ "lpAddressStatus", lpAddressStatus }
};
FUNC_INFO info =
{
"TSPI_lineGetAddressStatus",
3,
params
};
#endif

LONG lResult = 0;
PDRVLINE pLine = (PDRVLINE) hdLine;


Prolog (&info);

lpAddressStatus->dwNeededSize =
lpAddressStatus->dwUsedSize = sizeof(LINEADDRESSSTATUS);

lpAddressStatus->dwNumActiveCalls = (pLine->htCall ? 1 : 0);
lpAddressStatus->dwAddressFeatures = LINEADDRFEATURE_MAKECALL;

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineGetCallAddressID(
HDRVCALL hdCall,
LPDWORD lpdwAddressID
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszhdCall, hdCall },
{ "lpdwAddressID", lpdwAddressID }
};
FUNC_INFO info =
{
"TSPI_lineGetCallAddressID",
2,
params
};
#endif


//
// We only support 1 address (id=0)
//

Prolog (&info);
*lpdwAddressID = 0;
return (Epilog (&info, 0));
}


LONG
TSPIAPI
TSPI_lineGetCallInfo(
HDRVCALL hdCall,
LPLINECALLINFO lpLineInfo
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszhdCall, hdCall },
{ "lpLineInfo", lpLineInfo }
};
FUNC_INFO info =
{
"TSPI_lineGetCallInfo",
2,
params
};
#endif
LONG lResult = 0;
PDRVLINE pLine = (PDRVLINE) hdCall;


Prolog (&info);

lpLineInfo->dwNeededSize =
lpLineInfo->dwUsedSize = sizeof(LINECALLINFO);

lpLineInfo->dwBearerMode = LINEBEARERMODE_VOICE;
lpLineInfo->dwMediaMode = pLine->dwMediaMode;
lpLineInfo->dwCallStates = LINECALLSTATE_IDLE |
LINECALLSTATE_DIALTONE |
LINECALLSTATE_DIALING |
LINECALLSTATE_CONNECTED |
LINECALLSTATE_PROCEEDING |
LINECALLSTATE_DISCONNECTED |
LINECALLSTATE_UNKNOWN;
lpLineInfo->dwOrigin = LINECALLORIGIN_OUTBOUND;
lpLineInfo->dwReason = LINECALLREASON_DIRECT;
lpLineInfo->dwCallerIDFlags =
lpLineInfo->dwCalledIDFlags =
lpLineInfo->dwConnectedIDFlags =
lpLineInfo->dwRedirectionIDFlags =
lpLineInfo->dwRedirectingIDFlags = LINECALLPARTYID_UNAVAIL;

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineGetCallStatus(
HDRVCALL hdCall,
LPLINECALLSTATUS lpLineStatus
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszhdCall, hdCall },
{ "lpLineStatus", lpLineStatus }
};
FUNC_INFO info =
{
"TSPI_lineGetCallStatus",
2,
params
};
#endif
LONG lResult = 0;
PDRVLINE pLine = (PDRVLINE) hdCall;


Prolog (&info);

lpLineStatus->dwNeededSize =
lpLineStatus->dwUsedSize = sizeof(LINECALLSTATUS);

lpLineStatus->dwCallState = pLine->dwCallState;

if (pLine->dwCallState != LINECALLSTATE_IDLE)
{
lpLineStatus->dwCallFeatures = LINECALLFEATURE_DROP;
}

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineGetDevCaps(
DWORD dwDeviceID,
DWORD dwTSPIVersion,
DWORD dwExtVersion,
LPLINEDEVCAPS lpLineDevCaps
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszdwDeviceID, dwDeviceID },
{ "dwTSPIVersion", dwTSPIVersion },
{ "dwExtVersion", dwExtVersion },
{ "lpLineDevCaps", lpLineDevCaps }
};
FUNC_INFO info =
{
"TSPI_lineGetDevCaps",
4,
params
};
#endif

LONG lResult = 0;
static WCHAR szProviderInfo[] = L"AT-compatible modem service provider";

#define PROVIDER_INFO_SIZE (37 * sizeof (WCHAR))

Prolog (&info);

lpLineDevCaps->dwNeededSize = sizeof (LINEDEVCAPS) + PROVIDER_INFO_SIZE +
(MAX_DEV_NAME_LENGTH + 1) * sizeof (WCHAR);

if (lpLineDevCaps->dwTotalSize >= lpLineDevCaps->dwNeededSize)
{
#define LINECONFIG_SIZE (2 * (MAX_DEV_NAME_LENGTH + 1) + 40)

char szLineConfig[LINECONFIG_SIZE], szLineN[16], *p;
HKEY hKey;
DWORD dwDataSize, dwDataType;


lpLineDevCaps->dwUsedSize = lpLineDevCaps->dwNeededSize;

lpLineDevCaps->dwProviderInfoSize = PROVIDER_INFO_SIZE;
lpLineDevCaps->dwProviderInfoOffset = sizeof(LINEDEVCAPS);

My_lstrcpyW ((WCHAR *)(lpLineDevCaps + 1), szProviderInfo);

RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
gszAtspKey,
0,
KEY_ALL_ACCESS,
&hKey
);

dwDataSize = LINECONFIG_SIZE;
wsprintf (szLineN, "Line%d", dwDeviceID - gdwLineDeviceIDBase);
lstrcpy (szLineConfig, gszDefLineConfigParams);

RegQueryValueEx(
hKey,
szLineN,
0,
&dwDataType,
(LPBYTE) szLineConfig,
&dwDataSize
);

RegCloseKey (hKey);

for (p = szLineConfig; *p != ','; p++);
*p = 0;

lpLineDevCaps->dwLineNameSize = (lstrlen (szLineConfig) + 1) *
sizeof (WCHAR);
lpLineDevCaps->dwLineNameOffset = sizeof(LINEDEVCAPS) +
PROVIDER_INFO_SIZE;

MultiByteToWideChar(
CP_ACP,
MB_PRECOMPOSED,
szLineConfig,
-1,
(WCHAR *) ((LPBYTE) (lpLineDevCaps + 1) + PROVIDER_INFO_SIZE),
lpLineDevCaps->dwLineNameSize
);
}
else
{
lpLineDevCaps->dwUsedSize = sizeof(LINEDEVCAPS);
}

lpLineDevCaps->dwStringFormat = STRINGFORMAT_ASCII;
lpLineDevCaps->dwAddressModes = LINEADDRESSMODE_ADDRESSID;
lpLineDevCaps->dwNumAddresses = 1;
lpLineDevCaps->dwBearerModes = LINEBEARERMODE_VOICE;
lpLineDevCaps->dwMaxRate = 9600;
lpLineDevCaps->dwMediaModes = LINEMEDIAMODE_INTERACTIVEVOICE |
LINEMEDIAMODE_DATAMODEM;
lpLineDevCaps->dwDevCapFlags = LINEDEVCAPFLAGS_CLOSEDROP |
LINEDEVCAPFLAGS_DIALBILLING |
LINEDEVCAPFLAGS_DIALQUIET |
LINEDEVCAPFLAGS_DIALDIALTONE;
lpLineDevCaps->dwMaxNumActiveCalls = 1;
lpLineDevCaps->dwRingModes = 1;
lpLineDevCaps->dwLineFeatures = LINEFEATURE_MAKECALL;

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineGetID(
HDRVLINE hdLine,
DWORD dwAddressID,
HDRVCALL hdCall,
DWORD dwSelect,
LPVARSTRING lpDeviceID,
LPCWSTR lpszDeviceClass,
HANDLE hTargetProcess
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszhdLine, hdLine },
{ "dwAddressID", dwAddressID },
{ gszhdCall, hdCall },
{ "dwSelect", dwSelect },
{ "lpDeviceID", lpDeviceID },
{ "lpszDeviceClass", lpszDeviceClass },
{ "hTargetProcess", hTargetProcess }
};
FUNC_INFO info =
{
"TSPI_lineGetID",
7,
params
};
#endif

DWORD dwNeededSize = sizeof(VARSTRING) + sizeof (DWORD);
LONG lResult = 0;
PDRVLINE pLine = (dwSelect == LINECALLSELECT_CALL ?
(PDRVLINE) hdCall : (PDRVLINE) hdLine);


Prolog (&info);

if (lstrcmpiW (lpszDeviceClass, L"tapi/line") == 0)
{
if (lpDeviceID->dwTotalSize < dwNeededSize)
{
lpDeviceID->dwUsedSize = 3*sizeof(DWORD);
}
else
{
lpDeviceID->dwUsedSize = dwNeededSize;

lpDeviceID->dwStringFormat = STRINGFORMAT_BINARY;
lpDeviceID->dwStringSize = sizeof(DWORD);
lpDeviceID->dwStringOffset = sizeof(VARSTRING);

*((LPDWORD)(lpDeviceID + 1)) = pLine->dwDeviceID;
}

lpDeviceID->dwNeededSize = dwNeededSize;
}
else if (lstrcmpiW (lpszDeviceClass, L"comm/datamodem") == 0)
{
dwNeededSize += (strlen (pLine->szComm) + 1) * sizeof (WCHAR);

if (lpDeviceID->dwTotalSize < dwNeededSize)
{
lpDeviceID->dwUsedSize = 3 * sizeof(DWORD);
}
else
{
HANDLE hCommDup = NULL;


if (!pLine->htCall)
{
DBGOUT((1, "TSPI_lineGetID32: error, no active call"));

lResult = LINEERR_OPERATIONFAILED;

goto TSPI_lineGetID_epilog;
}

if (!DuplicateHandle(
GetCurrentProcess(),
pLine->hComm,
hTargetProcess,
&hCommDup,
0,
TRUE,
DUPLICATE_SAME_ACCESS
))
{
DBGOUT((
1,
"TSPI_lineGetID: DupHandle failed, err=%ld",
GetLastError()
));

lResult = LINEERR_OPERATIONFAILED;

goto TSPI_lineGetID_epilog;
}

lpDeviceID->dwUsedSize = dwNeededSize;

lpDeviceID->dwStringFormat = STRINGFORMAT_BINARY;
lpDeviceID->dwStringSize = dwNeededSize - sizeof(VARSTRING);
lpDeviceID->dwStringOffset = sizeof(VARSTRING);

*((HANDLE *)(lpDeviceID + 1)) = hCommDup;

lstrcpy(
((char *)(lpDeviceID + 1)) + sizeof (HANDLE),
pLine->szComm
);

MultiByteToWideChar(
CP_ACP,
0,
pLine->szComm,
-1,
((WCHAR *)(lpDeviceID + 1)) + sizeof (HANDLE),
256
);
}

lpDeviceID->dwNeededSize = dwNeededSize;
}
else
{
lResult = LINEERR_NODEVICE;
}

TSPI_lineGetID_epilog:

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineGetLineDevStatus(
HDRVLINE hdLine,
LPLINEDEVSTATUS lpLineDevStatus
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszhdLine, hdLine },
{ "lpLineDevStatus", lpLineDevStatus }
};
FUNC_INFO info =
{
"TSPI_lineGetLineDevStatus",
2,
params
};
#endif

LONG lResult = 0;
PDRVLINE pLine = (PDRVLINE) hdLine;


Prolog (&info);

lpLineDevStatus->dwUsedSize =
lpLineDevStatus->dwNeededSize = sizeof (LINEDEVSTATUS);

lpLineDevStatus->dwNumActiveCalls = (pLine->htCall ? 1 : 0);
//lpLineDevStatus->dwLineFeatures =
lpLineDevStatus->dwDevStatusFlags = LINEDEVSTATUSFLAGS_CONNECTED |
LINEDEVSTATUSFLAGS_INSERVICE;
return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineGetNumAddressIDs(
HDRVLINE hdLine,
LPDWORD lpdwNumAddressIDs
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszhdLine, hdLine },
{ "lpdwNumAddressIDs", lpdwNumAddressIDs }
};
FUNC_INFO info =
{
"TSPI_lineGetNumAddressIDs",
2,
params
};
#endif

LONG lResult = 0;
PDRVLINE pLine = (PDRVLINE) hdLine;


//
// We only support 1 address (id=0)
//

Prolog (&info);
*lpdwNumAddressIDs = 1;
return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineMakeCall(
DRV_REQUESTID dwRequestID,
HDRVLINE hdLine,
HTAPICALL htCall,
LPHDRVCALL lphdCall,
LPCWSTR lpszDestAddress,
DWORD dwCountryCode,
LPLINECALLPARAMS const lpCallParams
)
{
char szCommands[64], szCommand[64], szDestAddress[128];
DWORD dwThreadID, dwNumBytes, dwError;
PDRVLINE pLine = (PDRVLINE) hdLine;
#if DBG
FUNC_PARAM params[] =
{
{ gszdwRequestID, dwRequestID },
{ gszhdLine, hdLine },
{ "htCall", htCall },
{ "lphdCall", lphdCall },
{ "lpszDestAddress", szDestAddress },
{ "dwCountryCode", dwCountryCode },
{ gszlpCallParams, lpCallParams }
};
FUNC_INFO info =
{
"TSPI_lineMakeCall",
7,
params
};
#endif


if (lpszDestAddress)
{
WideCharToMultiByte(
CP_ACP,
0,
lpszDestAddress,
-1,
(LPSTR) szDestAddress,
128,
NULL,
NULL
);
}

Prolog (&info);


//
// Check to see if there's already another call
//

if (pLine->htCall)
{
(*gpfnCompletionProc)(dwRequestID, LINEERR_CALLUNAVAIL);
goto TSPI_lineMakeCall_return;
}


//
// Since we don't support TSPI_lineDial, fail if app tries
// to pass a NULL lpszDestAddress (implying that app just
// wants to go offhook)
//

if (lpszDestAddress == NULL)
{
(*gpfnCompletionProc)(dwRequestID, LINEERR_INVALADDRESS);
goto TSPI_lineMakeCall_return;
}


//
// Get the line's config info
//

{
HKEY hKey;
DWORD dwDataSize, dwDataType;
char szLineN[8], *pszConfig, *p, *p2;


wsprintf(
szLineN,
"Line%d",
((PDRVLINE) hdLine)->dwDeviceID - gdwLineDeviceIDBase
);

dwDataSize = 256;

pszConfig = DrvAlloc (dwDataSize);

RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
gszAtspKey,
0,
KEY_ALL_ACCESS,
&hKey
);

RegQueryValueEx(
hKey,
szLineN,
0,
&dwDataType,
(LPBYTE) pszConfig,
&dwDataSize
);

pszConfig[dwDataSize] = '\0'; // *pszConfig = "MyLine,COM1,L0"

RegCloseKey (hKey);


//
// szComm
//

for (p = pszConfig; *p != ','; p++);
p++; // *p = "COM1,L0"
for (p2 = p; *p2 != ','; p2++);
*p2 = 0; // *p = "COM1"

lstrcpy (pLine->szComm, p);


//
// szCommands
//

p2++; // *p2 = "L0"
lstrcpy (szCommands, p2);

DrvFree (pszConfig);
}


//
// Open the port
//

if ((pLine->hComm = CreateFile(
pLine->szComm,
GENERIC_READ | GENERIC_WRITE,
0, //FILE_SHARE_READ | FILE_SHARE_WRITE, // BUGBUG
NULL, // no security attrs
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL // no template file

)) == INVALID_HANDLE_VALUE)
{
DBGOUT((
3,
"TSPI_lineMakeCall: CreateFile(%s) failed, err=%ld",
pLine->szComm,
GetLastError()
));

(*gpfnCompletionProc)(dwRequestID, LINEERR_RESOURCEUNAVAIL);
goto TSPI_lineMakeCall_return;
}


//
// Setup up the modem command string. If there's an initial 'T'
// or 'P' (for Tone or Pulse) in the dest address then disregard
// it. Also if it's a voice call add the semi colon so we return
// to cmd mode.
//

{
char *p = (char *) szDestAddress;


if (*p == 'T' || *p == 'P')
{
p++;
}

if (lpCallParams &&
lpCallParams->dwMediaMode != LINEMEDIAMODE_INTERACTIVEVOICE)
{
wsprintf (szCommand, "AT%sDT%s\r", szCommands, p);
}
else
{
wsprintf (szCommand, "AT%sDT%s;\r", szCommands, p);
}
}


//
// Init the data structure & tell tapi our handle to the call
//

pLine->htCall = htCall;
pLine->bDropInProgress = FALSE;
pLine->dwMediaMode = (lpCallParams ? lpCallParams->dwMediaMode :
LINEMEDIAMODE_INTERACTIVEVOICE);

*lphdCall = (HDRVCALL) pLine;


//
// Do an overlapped write, the comm thread will deal with the results
//

pLine->Overlapped.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);

if (!WriteFile(
pLine->hComm,
szCommand,
lstrlen (szCommand),
&dwNumBytes,
&pLine->Overlapped
)

&& (dwError = GetLastError()) != ERROR_IO_PENDING)
{
DBGOUT((
1,
"TSPI_lineMakeCall: WriteFile(%s) failed, error=%d",
pLine->szComm,
dwError
));

pLine->htCall = NULL;
CloseHandle (pLine->hComm);
CloseHandle (pLine->Overlapped.hEvent);
(*gpfnCompletionProc)(dwRequestID, LINEERR_OPERATIONFAILED);
goto TSPI_lineMakeCall_return;
}


//
// Complete the requests & set the initial call state
//

(*gpfnCompletionProc)(dwRequestID, 0);
SetCallState (pLine, LINECALLSTATE_DIALING, 0);


//
// Spin the comm thread to handle the results of the Write above
//

{
HANDLE hCommThread;


if (!(hCommThread = CreateThread(
(LPSECURITY_ATTRIBUTES) NULL,
0,
(LPTHREAD_START_ROUTINE) CommThread,
pLine,
0,
&dwThreadID
)))
{
DBGOUT((
1,
"TSPI_lineMakeCall: CreateThread failed, err=%ld",
GetLastError()
));

GetOverlappedResult(
pLine->hComm,

&pLine->Overlapped, 
&dwNumBytes,
TRUE
);

SetCallState (pLine, LINECALLSTATE_IDLE, 0);
CloseHandle (pLine->hComm);
CloseHandle (pLine->Overlapped.hEvent);
goto TSPI_lineMakeCall_return;
}

CloseHandle (hCommThread);
}


TSPI_lineMakeCall_return:

return (Epilog (&info, dwRequestID));
}


LONG
TSPIAPI
TSPI_lineNegotiateTSPIVersion(
DWORD dwDeviceID,
DWORD dwLowVersion,
DWORD dwHighVersion,
LPDWORD lpdwTSPIVersion
)
{
LONG lResult = 0;
#if DBG
FUNC_PARAM params[] =
{
{ gszdwDeviceID, dwDeviceID },
{ "dwLowVersion", dwLowVersion },
{ "dwHighVersion", dwHighVersion },
{ "lpdwTSPIVersion", lpdwTSPIVersion }
};
FUNC_INFO info =
{
"TSPI_lineNegotiateTSPIVersion",
4,
params
};
#endif

Prolog (&info);
*lpdwTSPIVersion = 0x00020000;
return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineOpen(
DWORD dwDeviceID,
HTAPILINE htLine,
LPHDRVLINE lphdLine,
DWORD dwTSPIVersion,
LINEEVENT lpfnEventProc
)
{
LONG lResult;
PDRVLINE pLine;
#if DBG
FUNC_PARAM params[] =
{
{ gszdwDeviceID, dwDeviceID },
{ "htLine", htLine },
{ "lphdLine", lphdLine },
{ "dwTSPIVersion", dwTSPIVersion },
{ "lpfnEventProc", lpfnEventProc }
};
FUNC_INFO info =
{
"TSPI_lineOpen",
5,
params
};
#endif


Prolog (&info);

if ((pLine = DrvAlloc (sizeof (DRVLINE))))
{
pLine->htLine = htLine;
pLine->pfnEventProc = lpfnEventProc;
pLine->dwDeviceID = dwDeviceID;

*lphdLine = (HDRVLINE) pLine;

lResult = 0;
}
else
{
lResult = LINEERR_NOMEM;
}

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_lineSetDefaultMediaDetection(
HDRVLINE hdLine,
DWORD dwMediaModes
)
{
#if DBG
FUNC_PARAM params[] =
{
{ gszhdLine, hdLine },
{ "dwMediaModes", dwMediaModes }
};
FUNC_INFO info =
{
"TSPI_lineSetDefaultMediaDetection",
2,
params
};
#endif


//
// This func is really a no-op for us, since we don't look
// for incoming calls (though we do say we support them to
// make apps happy)
//

Prolog (&info);
return (Epilog (&info, 0));
}


//
// ------------------------- TSPI_providerXxx funcs ---------------------------
//

LONG
TSPIAPI
TSPI_providerConfig(
HWND hwndOwner,
DWORD dwPermanentProviderID
)
{
//
// Although this func is never called by TAPI v2.0, we export
// it so that the Telephony Control Panel Applet knows that it
// can configure this provider via lineConfigProvider(),
// otherwise Telephon.cpl will not consider it configurable
//

return 0;
}


LONG
TSPIAPI
TSPI_providerGenericDialogData(
DWORD dwObjectID,
DWORD dwObjectType,
LPVOID lpParams,
DWORD dwSize
)
{
LONG lResult = 0;
#if DBG
FUNC_PARAM params[] =
{
{ "dwObjectID", dwObjectID },
{ "dwObjectType", dwObjectType },
{ "lpParams", lpParams },
{ "dwSize", dwSize }
};
FUNC_INFO info =
{
"TSPI_providerGenericDialogData",
4,
params
};
#endif


Prolog (&info);
return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_providerInit(
DWORD dwTSPIVersion,
DWORD dwPermanentProviderID,
DWORD dwLineDeviceIDBase,
DWORD dwPhoneDeviceIDBase,
DWORD dwNumLines,
DWORD dwNumPhones,
ASYNC_COMPLETION lpfnCompletionProc,
LPDWORD lpdwTSPIOptions
)
{
LONG lResult = 0;
#if DBG
FUNC_PARAM params[] =
{
{ "dwTSPIVersion", dwTSPIVersion },
{ gszdwPermanentProviderID, dwPermanentProviderID },
{ "dwLineDeviceIDBase", dwLineDeviceIDBase },
{ "dwPhoneDeviceIDBase", dwPhoneDeviceIDBase },
{ "dwNumLines", dwNumLines },
{ "dwNumPhones", dwNumPhones },
{ "lpfnCompletionProc", lpfnCompletionProc }
};
FUNC_INFO info =
{
"TSPI_providerInit",
7,
params
};
#endif

Prolog (&info);
gdwLineDeviceIDBase = dwLineDeviceIDBase;
gpfnCompletionProc = lpfnCompletionProc;
*lpdwTSPIOptions = LINETSPIOPTION_NONREENTRANT;
return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_providerInstall(
HWND hwndOwner,
DWORD dwPermanentProviderID
)
{
//
// Although this func is never called by TAPI v2.0, we export
// it so that the Telephony Control Panel Applet knows that it
// can add this provider via lineAddProvider(), otherwise
// Telephon.cpl will not consider it installable
//
//

return 0;
}


LONG
TSPIAPI
TSPI_providerRemove(
HWND hwndOwner,
DWORD dwPermanentProviderID
)
{
//
// Although this func is never called by TAPI v2.0, we export
// it so that the Telephony Control Panel Applet knows that it
// can remove this provider via lineRemoveProvider(), otherwise
// Telephon.cpl will not consider it removable
//

return 0;
}


LONG
TSPIAPI
TSPI_providerShutdown(
DWORD dwTSPIVersion,
DWORD dwPermanentProviderID
)
{
LONG lResult = 0;
#if DBG
FUNC_PARAM params[] =
{
{ "dwTSPIVersion", dwTSPIVersion },
{ gszdwPermanentProviderID, dwPermanentProviderID }
};
FUNC_INFO info =
{
"TSPI_providerShutdown",
2,
params
};
#endif


Prolog (&info);

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TSPI_providerEnumDevices(
DWORD dwPermanentProviderID,
LPDWORD lpdwNumLines,
LPDWORD lpdwNumPhones,
HPROVIDER hProvider,
LINEEVENT lpfnLineCreateProc,
PHONEEVENT lpfnPhoneCreateProc
)
{
HKEY hKey;
DWORD dwNumLines, dwDataType, dwDataSize;


//
// Retrieve the number of devices we're
// configured for from our registry section
//

RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
gszAtspKey,
0,
KEY_ALL_ACCESS,
&hKey
);

dwDataSize = sizeof(dwNumLines);
dwNumLines = 0;

RegQueryValueEx(
hKey,
gszNumLines,
0,
&dwDataType,
(LPBYTE) &dwNumLines,
&dwDataSize
);

RegCloseKey (hKey);

*lpdwNumLines = dwNumLines;
*lpdwNumPhones = 0;
return 0;
}


LONG
TSPIAPI
TSPI_providerUIIdentify(
LPWSTR lpszUIDLLName
)
{
LONG lResult = 0;
#if DBG
FUNC_PARAM params[] =
{
{ "lpsUIDLLName", lpszUIDLLName }
};
FUNC_INFO info =
{
"TSPI_providerUIIdentify",
1,
params
};
#endif


Prolog (&info);
My_lstrcpyW(lpszUIDLLName, L"atsp32.tsp");
return (Epilog (&info, lResult));
}


//
// ---------------------------- TUISPI_xxx funcs ------------------------------
//

LONG
TSPIAPI
TUISPI_lineConfigDialog(
TUISPIDLLCALLBACK lpfnUIDLLCallback,
DWORD dwDeviceID,
HWND hwndOwner,
LPCWSTR lpszDeviceClass
)
{
char szDeviceClass[128];
LONG lResult = 0;
#if DBG
FUNC_PARAM params[] =
{
{ "lpfnUIDLLCallback", lpfnUIDLLCallback },
{ gszdwDeviceID, dwDeviceID },
{ gszhwndOwner, hwndOwner },
{ "lpszDeviceClass", szDeviceClass }
};
FUNC_INFO info =
{
"TUISPI_lineConfigDialog",
4,
params
};
#endif


if (lpszDeviceClass)
{
WideCharToMultiByte(
CP_ACP,
0,
lpszDeviceClass,
-1,
(LPSTR) szDeviceClass,
128,
NULL,
NULL
);
}

Prolog (&info);

DialogBoxParam(
ghInst,
MAKEINTRESOURCE(IDD_DIALOG1),
hwndOwner,
(DLGPROC) ConfigDlgProc,
0
);

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TUISPI_providerConfig(
TUISPIDLLCALLBACK lpfnUIDLLCallback,
HWND hwndOwner,
DWORD dwPermanentProviderID
)
{
LONG lResult = 0;
#if DBG
FUNC_PARAM params[] =
{
{ "lpfnUIDLLCallback", lpfnUIDLLCallback },
{ gszhwndOwner, hwndOwner },
{ gszdwPermanentProviderID, dwPermanentProviderID }
};
FUNC_INFO info =
{
"TUISPI_providerConfig",
3,
params
};
#endif


Prolog (&info);

DialogBoxParam(
ghInst,
MAKEINTRESOURCE(IDD_DIALOG1),
hwndOwner,
(DLGPROC) ConfigDlgProc,
0
);

return (Epilog (&info, lResult));
}


LONG
TSPIAPI
TUISPI_providerInstall(
TUISPIDLLCALLBACK lpfnUIDLLCallback,
HWND hwndOwner,
DWORD dwPermanentProviderID
)
{
LONG lResult;


if ((lResult = ProviderInstall ("atsp32.tsp", TRUE)) == 0)
{
DialogBoxParam(
ghInst,
MAKEINTRESOURCE(IDD_DIALOG1),
hwndOwner,
(DLGPROC) ConfigDlgProc,
0
);
}

return lResult;
}


LONG
TSPIAPI
TUISPI_providerRemove(
TUISPIDLLCALLBACK lpfnUIDLLCallback,
HWND hwndOwner,
DWORD dwPermanentProviderID
)
{
HKEY hKey;
char szSoftwareMsft[] = "Software\\Microsoft", szATSP[] = "ATSP";


//
// Clean up our registry section
//

RegOpenKeyExA(
HKEY_LOCAL_MACHINE,
szSoftwareMsft,
0,
KEY_ALL_ACCESS,
&hKey
);

RegDeleteKeyA (hKey, szATSP);
RegCloseKey (hKey);
return 0;
}


#pragma warning (default:4047)


//
// ---------------------- Misc private support routines -----------------------
//

LPWSTR
PASCAL
My_lstrcpyW(
WCHAR *pString1,
WCHAR *pString2
)
{
WCHAR *p = pString1;


for (; (*p = *pString2); p++, pString2++);
return pString1;
}


void
PASCAL
EnableChildren(
HWND hwnd,
BOOL bEnable
)
{
int i;
static int aiControlIDs[] =
{
IDC_DEVICES,
IDC_NAME,
IDC_PORT,
IDC_COMMANDS,
IDC_REMOVE,
0
};


for (i = 0; aiControlIDs[i]; i++)
{
EnableWindow (GetDlgItem (hwnd, aiControlIDs[i]), bEnable);
}
}


void
PASCAL
SelectDevice(
HWND hwnd,
int iDevice
)
{
SendDlgItemMessage (hwnd, IDC_DEVICES, LB_SETCURSEL, iDevice, 0);
PostMessage(hwnd, WM_COMMAND, IDC_DEVICES | (LBN_SELCHANGE << 16), 0);
}


BOOL
CALLBACK
ConfigDlgProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam
)
{
static HKEY hAtspKey;

DWORD dwDataSize;
DWORD dwDataType;


switch (msg)
{
case WM_INITDIALOG:
{
char *pBuf;
DWORD i, iNumLines;


//
// Create or open our configuration key in the registry. If the
// create fails it may well be that the current user does not
// have write access to this portion of the registry, so we'll
// just show a "read only" dialog and not allow user to make any
// changes
//

{
LONG lResult;
DWORD dwDisposition;


if ((lResult = RegCreateKeyEx(
HKEY_LOCAL_MACHINE,
gszAtspKey,
0,
"",
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
(LPSECURITY_ATTRIBUTES) NULL,
&hAtspKey,
&dwDisposition

)) != ERROR_SUCCESS)
{
DBGOUT((
3,
"RegCreateKeyEx(%s,ALL_ACCESS) failed, err=%d",
gszAtspKey,
lResult
));

if ((lResult = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
gszAtspKey,
0,
KEY_QUERY_VALUE,
&hAtspKey

)) != ERROR_SUCCESS)
{
DBGOUT((
3,
"RegOpenKeyEx(%s,ALL_ACCESS) failed, err=%d",
gszAtspKey,
lResult
));

EndDialog (hwnd, 0);
return FALSE;
}

{
int i;
static int aiControlIDs[] =
{
IDC_NAME,
IDC_PORT,
IDC_COMMANDS,
IDC_ADD,
IDC_REMOVE,
IDOK,
0
};


for (i = 0; aiControlIDs[i]; i++)
{
EnableWindow(
GetDlgItem (hwnd, aiControlIDs[i]),
FALSE
);
}
}
}
}


//
// Retrieve our configuration info from the registry
//

dwDataSize = sizeof(iNumLines);
iNumLines = 0;

RegQueryValueEx(
hAtspKey,
gszNumLines,
0,
&dwDataType,
(LPBYTE) &iNumLines,
&dwDataSize
);

SendDlgItemMessage(
hwnd,
IDC_NAME,
EM_LIMITTEXT,
MAX_DEV_NAME_LENGTH,
0
);

SendDlgItemMessage(
hwnd,
IDC_COMMANDS,
EM_LIMITTEXT,
MAX_DEV_NAME_LENGTH,
0
);

pBuf = DrvAlloc (256);

for (i = 0; i < iNumLines; i++)
{
char *p, *p2, szLineN[8];
PDRVLINECONFIG pLineConfig = DrvAlloc (sizeof(DRVLINECONFIG));
LONG lResult;


wsprintf (szLineN, "Line%d", i);

dwDataSize = 256;

lResult = RegQueryValueEx(
hAtspKey,
szLineN,
0,
&dwDataType,
(LPBYTE) pBuf,
&dwDataSize
);


//
// If there was a problem, use the default config
//

if (0 != lResult)
{
lstrcpy (pBuf, gszDefLineConfigParams);
}

for (p = pBuf; *p != ','; p++);
*p = 0;

SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_ADDSTRING,
0,
(LPARAM) pBuf
);

SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_SETITEMDATA,
i,
(LPARAM) pLineConfig
);

p++;
for (p2 = p; *p2 != ','; p2++);
*p2 = 0;

lstrcpy (pLineConfig->szPort, p);

p = p2 + 1;

lstrcpy (pLineConfig->szCommands, p);
}

DrvFree (pBuf);


//
// Fill up the various controls with configuration options
//

{
static char *aszPorts[] = { "COM1","COM2","COM3",NULL };

for (i = 0; aszPorts[i]; i++)
{
SendDlgItemMessage(
hwnd,
IDC_PORT,
LB_ADDSTRING,
0,
(LPARAM) aszPorts[i]
);
}
}

if (iNumLines == 0)
{
EnableChildren (hwnd, FALSE);
}
else
{
SelectDevice (hwnd, 0);
}

break;
}
case WM_COMMAND:
{
int iSelection;
PDRVLINECONFIG pLineConfig;


iSelection = SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_GETCURSEL,
0,
0
);

pLineConfig = (PDRVLINECONFIG) SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_GETITEMDATA,
(WPARAM) iSelection,
0
);

switch (LOWORD((DWORD)wParam))
{
case IDC_DEVICES:

if (HIWORD(wParam) == LBN_SELCHANGE)
{
char buf[MAX_DEV_NAME_LENGTH + 1];


SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_GETTEXT,
iSelection,
(LPARAM) buf
);

SetDlgItemText (hwnd, IDC_NAME, buf);

SendDlgItemMessage(
hwnd,
IDC_PORT,
LB_SELECTSTRING,
(WPARAM) -1,
(LPARAM) pLineConfig->szPort
);

SetDlgItemText (hwnd, IDC_COMMANDS, pLineConfig->szCommands);
}

break;

case IDC_NAME:

if ((HIWORD(wParam) == EN_CHANGE) && (iSelection != LB_ERR))
{
char buf[MAX_DEV_NAME_LENGTH + 1];


GetDlgItemText (hwnd, IDC_NAME, buf, MAX_DEV_NAME_LENGTH);

SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_DELETESTRING,
iSelection,
0
);

SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_INSERTSTRING,
iSelection,
(LPARAM) buf
);

SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_SETCURSEL,
iSelection,
0
);

SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_SETITEMDATA,
iSelection,
(LPARAM) pLineConfig
);
}

break;

case IDC_PORT:

if (HIWORD(wParam) == LBN_SELCHANGE)
{
iSelection = SendDlgItemMessage(
hwnd,
IDC_PORT,
LB_GETCURSEL,
0,
0
);

SendDlgItemMessage(
hwnd,
IDC_PORT,
LB_GETTEXT,
iSelection,
(LPARAM) pLineConfig->szPort
);
}

break;

case IDC_COMMANDS:

if ((HIWORD(wParam) == EN_CHANGE) && (iSelection != LB_ERR))
{
GetDlgItemText(
hwnd,
IDC_COMMANDS,
pLineConfig->szCommands,
63
);
}

break;

case IDC_ADD:
{
int iNumLines, i = 2;
char szLineName[32];
PDRVLINECONFIG pLineConfig = DrvAlloc (sizeof(DRVLINECONFIG));


iNumLines = SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_GETCOUNT,
0,
0
);

lstrcpy (pLineConfig->szPort, "COM1");

lstrcpy (szLineName, "my new line");

find_unique_line_name:

if (SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_FINDSTRING,
(WPARAM) -1,
(LPARAM) szLineName

) != LB_ERR)
{
wsprintf (szLineName, "my new line%d", i++);
goto find_unique_line_name;
}

SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_ADDSTRING,
0,
(LPARAM) szLineName
);

SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_SETITEMDATA,
iNumLines,
(LPARAM) pLineConfig
);

EnableChildren (hwnd, TRUE);

SelectDevice (hwnd, iNumLines);

SetFocus (GetDlgItem (hwnd, IDC_NAME));

SendDlgItemMessage(
hwnd,
IDC_NAME,
EM_SETSEL,
0,
(LPARAM) -1
);

break;
}
case IDC_REMOVE:
{
int iNumLines;


DrvFree (pLineConfig);

iNumLines = SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_DELETESTRING,
iSelection,
0
);

if (iNumLines == 0)
{
SetDlgItemText (hwnd, IDC_NAME, "");
SetDlgItemText (hwnd, IDC_COMMANDS, "");

EnableChildren (hwnd, FALSE);
}
else
{
SelectDevice (hwnd, 0);
}

break;
}
case IDOK:
{
int i, iNumLines;
char *pBuf;


//
// Update the num lines & num phones values
//

pBuf = DrvAlloc (256);

iNumLines = SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_GETCOUNT,
0,
0
);

RegSetValueEx(
hAtspKey,
gszNumLines,
0,
REG_DWORD,
(LPBYTE) &iNumLines,
sizeof(DWORD)
);


//
// For each installed device save it's config info
//

for (i = 0; i < iNumLines; i++)
{
char szLineN[8];
PDRVLINECONFIG pLineConfig;


SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_GETTEXT,
i,
(LPARAM) pBuf
);

pLineConfig = (PDRVLINECONFIG) SendDlgItemMessage(
hwnd,
IDC_DEVICES,
LB_GETITEMDATA,
i,
0
);

wsprintf(
pBuf + strlen (pBuf),
",%s,%s",
pLineConfig->szPort,
pLineConfig->szCommands
);

wsprintf (szLineN, "Line%d", i);

RegSetValueEx(
hAtspKey,
szLineN,
0,
REG_SZ,
(LPBYTE) pBuf,
lstrlen (pBuf) + 1
);

DrvFree (pLineConfig);
}

DrvFree (pBuf);

// fall thru to EndDialog...
}
case IDCANCEL:

RegCloseKey (hAtspKey);
EndDialog (hwnd, 0);
break;

} // switch (LOWORD((DWORD)wParam))

break;
}
} // switch (msg)

return FALSE;
}


LPVOID
PASCAL
DrvAlloc(
DWORD dwSize
)
{
return (LocalAlloc (LPTR, dwSize));
}


VOID
PASCAL
DrvFree(
LPVOID lp
)
{
LocalFree (lp);
}


void
PASCAL
SetCallState(
PDRVLINE pLine,
DWORD dwCallState,
DWORD dwCallStateMode
)
{
if (dwCallState != pLine->dwCallState)
{
pLine->dwCallState = dwCallState;
pLine->dwCallStateMode = dwCallStateMode;

(*pLine->pfnEventProc)(
pLine->htLine,
pLine->htCall,
LINE_CALLSTATE,
dwCallState,
dwCallStateMode,
pLine->dwMediaMode
);
}
}


#if DBG

void
PASCAL
Prolog(
PFUNC_INFO pInfo
)
{
DWORD i;


DBGOUT((3, "%s: enter", pInfo->lpszFuncName));

for (i = 0; i < pInfo->dwNumParams; i++)
{
if (pInfo->aParams[i].dwVal &&
pInfo->aParams[i].lpszVal[3] == 'z') // lpszVal = "lpsz..."
{
DBGOUT((
3,
"%s%s=x%lx, '%s'",
gszTab,
pInfo->aParams[i].lpszVal,
pInfo->aParams[i].dwVal,
pInfo->aParams[i].dwVal
));
}
else
{
DBGOUT((
3,
"%s%s=x%lx",
gszTab,
pInfo->aParams[i].lpszVal,
pInfo->aParams[i].dwVal
));
}
}
}


LONG
PASCAL
Epilog(
PFUNC_INFO pInfo,
LONG lResult
)
{
DBGOUT((3, "%s: returning x%x", pInfo->lpszFuncName, lResult));

return lResult;
}


void
CDECL
DebugOutput(
DWORD dwDbgLevel,
LPCSTR lpszFormat,
...
)
{
if (dwDbgLevel <= gdwDebugLevel)
{
char buf[128] = "ATSP32: ";
va_list ap;


va_start(ap, lpszFormat);

wvsprintf (&buf[8], lpszFormat, ap);

lstrcat (buf, "\n");

OutputDebugString (buf);

va_end(ap);
}
}

#endif


LONG
PASCAL
ProviderInstall(
char *pszProviderName,
BOOL bNoMultipleInstance
)
{
LONG lResult;


//
// If only one installation instance of this provider is
// allowed then we want to check the provider list to see
// if the provider is already installed
//

if (bNoMultipleInstance)
{
LONG (WINAPI *pfnGetProviderList)();
DWORD dwTotalSize, i;
HINSTANCE hTapi32;

LPLINEPROVIDERLIST  pProviderList; 
LPLINEPROVIDERENTRY pProviderEntry;


//
// Load Tapi32.dll & get a pointer to the lineGetProviderList
// func. We don't want to statically link because this module
// plays the part of both core SP & UI DLL, and we don't want
// to incur the performance hit of automatically loading
// Tapi32.dll when running as a core SP within Tapisrv.exe's
// context.
//

if (!(hTapi32 = LoadLibrary ("tapi32.dll")))
{
DBGOUT((
1,
"LoadLibrary(tapi32.dll) failed, err=%d",
GetLastError()
));

lResult = LINEERR_OPERATIONFAILED;
goto ProviderInstall_return;
}

if (!((FARPROC) pfnGetProviderList = GetProcAddress(
hTapi32,
(LPCSTR) "lineGetProviderList"
)))
{
DBGOUT((
1,
"GetProcAddr(lineGetProviderList) failed, err=%d",
GetLastError()
));

lResult = LINEERR_OPERATIONFAILED;
goto ProviderInstall_unloadTapi32;
}


//
// Loop until we get the full provider list
//

dwTotalSize = sizeof (LINEPROVIDERLIST);

goto ProviderInstall_allocProviderList;

ProviderInstall_getProviderList:

if ((lResult = (*pfnGetProviderList)(0x00020000, pProviderList)) != 0)
{
goto ProviderInstall_freeProviderList;
}

if (pProviderList->dwNeededSize > pProviderList->dwTotalSize)
{
dwTotalSize = pProviderList->dwNeededSize;

LocalFree (pProviderList);

ProviderInstall_allocProviderList:

if (!(pProviderList = LocalAlloc (LPTR, dwTotalSize)))
{
lResult = LINEERR_NOMEM;
goto ProviderInstall_unloadTapi32;
}

pProviderList->dwTotalSize = dwTotalSize;

goto ProviderInstall_getProviderList;
}


//
// Inspect the provider list entries to see if this provider
// is already installed
//

pProviderEntry = (LPLINEPROVIDERENTRY) (((LPBYTE) pProviderList) +
pProviderList->dwProviderListOffset);

for (i = 0; i < pProviderList->dwNumProviders; i++)
{
char *pszInstalledProviderName = ((char *) pProviderList) +
pProviderEntry->dwProviderFilenameOffset,
*p;


//
// Make sure pszInstalledProviderName points at <filename>
// and not <path>\filename by walking backeards thru the
// string searching for last '\\'
//

p = pszInstalledProviderName +
lstrlen (pszInstalledProviderName) - 1;

for (; *p != '\\' && p != pszInstalledProviderName; p--);

pszInstalledProviderName =
(p == pszInstalledProviderName ? p : p + 1);

if (lstrcmpiA (pszInstalledProviderName, pszProviderName) == 0)
{
lResult = LINEERR_NOMULTIPLEINSTANCE;
goto ProviderInstall_freeProviderList;
}

pProviderEntry++;
}


//
// If here then the provider isn't currently installed,
// so do whatever configuration stuff is necessary and
// indicate SUCCESS
//

lResult = 0;


ProviderInstall_freeProviderList:

LocalFree (pProviderList);

ProviderInstall_unloadTapi32:

FreeLibrary (hTapi32);
}
else
{
//
// Do whatever configuration stuff is necessary and return SUCCESS
//

lResult = 0;
}

ProviderInstall_return:

return lResult;
}


void
PASCAL
DropActiveCall(
PDRVLINE pLine
)
{
if (pLine->hComm)
{
DWORD dwNumBytes, dwError;
OVERLAPPED overlapped;


pLine->bDropInProgress = TRUE;

SetEvent (pLine->Overlapped.hEvent);

ZeroMemory (&overlapped, sizeof (OVERLAPPED));

overlapped.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);

if (pLine->dwMediaMode != LINEMEDIAMODE_INTERACTIVEVOICE)
{
if (!WriteFile(
pLine->hComm,
"+++\r", 4,
&dwNumBytes,
&overlapped
))
{
if ((dwError = GetLastError()) == ERROR_IO_PENDING)
{
GetOverlappedResult(
pLine->hComm,
&overlapped,
&dwNumBytes,
TRUE
);

ResetEvent (overlapped.hEvent);
}
else
{
}
}
}

if (!WriteFile (pLine->hComm, "ATH\r", 4, &dwNumBytes, &overlapped))
{
if ((dwError = GetLastError()) == ERROR_IO_PENDING)
{
GetOverlappedResult(
pLine->hComm,
&overlapped,
&dwNumBytes,
TRUE
);
}
else
{
}
}

CloseHandle (overlapped.hEvent);
CloseHandle (pLine->hComm);
pLine->hComm = NULL;
}
}