TAPICODE.C

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1995 - 1998 Microsoft Corporation. All Rights Reserved.
//
// MODULE: TapiCode.c
//
// PURPOSE: Handles all the TAPI routines for TapiComm.
//
//
// EXPORTED FUNCTIONS: These functions are for use by other modules.
//
// InitializeTAPI - Initialize this app with TAPI.
// ShutdownTAPI - Shutdown this app from TAPI.
// DialCall - Dial a Call.
// HangupCall - Hangup an existing Call.
// PostHangupCall - Posts a HangupCall message to the main window.
//
// INTERNAL FUNCTIONS: These functions are for this module only.
//
// DialCallInParts - Actually Dial the call.
//
// lineCallbackFunc - TAPI callback for async messages.
//
// CheckAndReAllocBuffer - Helper function for I_ wrappers functions.
//
// I_lineNegotiateAPIVersion - Wrapper for lineNegotiateAPIVersion.
// I_lineGetDevCaps - Wrapper for lineGetDevCaps.
// I_lineGetAddressStatus - Wrapper for lineGetAddressStatus.
// I_lineTranslateAddress - Wrapper for lineTranslateAddress.
// I_lineGetCallStatus - Wrapper for lineGetCallStatus.
// I_lineGetAddressCaps - Wrapper for lineGetAddressCaps.
//
// WaitForCallState - Resynchronize by Waiting for a CallState.
// WaitForReply - Resynchronize by Waiting for a LINE_REPLY.
//
// DoLineReply - Handle asynchronous LINE_REPLY.
// DoLineClose - Handle asynchronous LINE_CLOSE.
// DoLineDevState - Handle asynchronous LINE_LINEDEVSTATE.
// DoLineCallState - Handle asynchronous LINE_CALLSTATE.
// DoLineCreate - Handle asynchronous LINE_CREATE.
//
// HandleLineErr - Handler for most LINEERR errors.
//
// HandleIniFileCorrupt - LINEERR handler for INIFILECORRUPT.
// HandleNoDriver - LINEERR handler for NODRIVER.
// HandleNoDevicesInstalled - LINEERR handler for NODEVICE.
// HandleReInit - LINEERR handler for REINIT.
// HandleNoMultipleInstance - LINEERR handler for NOMULTIPLEINSTANCE.
// HandleNoMem - LINEERR handler for NOMEM.
// HandleOperationFailed - LINEERR handler for OPERATIONFAILED.
// HandleResourceUnavail - LINEERR handler for RESOURCEUNAVAIL.
//
// LaunchModemControlPanelAdd - Launches the Modem Control Panel.
//
// WarningBox - Warn user if a line in use is removed.
//
// GetAddressToDial - Launches a GetAddressToDial dialog.
// DialDialogProc - Dialog Proc for the GetAddressToDial API.
//
// I_lineNegotiateLegacyAPIVersion - Wrapper to negoitiate with legacy TSPs
// VerifyUsableLine - Verify that a line device is usable
// FillTAPILine - Fill a combobox with TAPI Device names
// VerifyAndWarnUsableLine - Verify and warn if a line device is usable
// FillCountryCodeList - Fill a combobox with country codes
// FillLocationInfo - Fill a combobox with current TAPI locations
// UseDialingRules - Enable/Disable dialing rules controls
// DisplayPhoneNumber - Create and display a valid phone number
// PreConfigureDevice - Preconfigure a device line


#include <tapi.h>
#include <windows.h>
#include <string.h>
#include "globals.h"
#include "TapiInfo.h"
#include "TapiCode.h"
#include "CommCode.h"
#include "resource.h"
#include "statbar.h"
#include "toolbar.h"


// All TAPI line functions return 0 for SUCCESS, so define it.
#define SUCCESS 0

// Possible return error for resynchronization functions.
#define WAITERR_WAITABORTED 1
#define WAITERR_WAITTIMEDOUT 2

// Reasons why a line device might not be usable by TapiComm.
#define LINENOTUSEABLE_ERROR 1
#define LINENOTUSEABLE_NOVOICE 2
#define LINENOTUSEABLE_NODATAMODEM 3
#define LINENOTUSEABLE_NOMAKECALL 4
#define LINENOTUSEABLE_ALLOCATED 5
#define LINENOTUSEABLE_INUSE 6
#define LINENOTUSEABLE_NOCOMMDATAMODEM 7

// Constant used in WaitForCallState when any new
// callstate message is acceptable.
#define I_LINECALLSTATE_ANY 0

// Wait up to 30 seconds for an async completion.
#define WAITTIMEOUT 30000

// TAPI version that this sample is designed to use.
#define SAMPLE_TAPI_VERSION 0x00010004


// Global TAPI variables.
HWND g_hWndMainWindow = NULL; // Apps main window.
HWND g_hDlgParentWindow = NULL; // This will be the parent of all dialogs.
HLINEAPP g_hLineApp = NULL;
DWORD g_dwNumDevs = 0;

// Global variable that holds the handle to a TAPI dialog
// that needs to be dismissed if line conditions change.
HWND g_hDialog = NULL;

// Global flags to prevent re-entrancy problems.
BOOL g_bShuttingDown = FALSE;
BOOL g_bStoppingCall = FALSE;
BOOL g_bInitializing = FALSE;


// This sample only supports one call in progress at a time.
BOOL g_bTapiInUse = FALSE;


// Data needed per call. This sample only supports one call.
HCALL g_hCall = NULL;
HLINE g_hLine = NULL;
DWORD g_dwDeviceID = 0;
DWORD g_dwAPIVersion = 0;
DWORD g_dwCallState = 0;
char g_szDisplayableAddress[1024] = "";
char g_szDialableAddress[1024] = "";
BOOL g_bConnected = FALSE;
LPVOID g_lpDeviceConfig = NULL;
DWORD g_dwSizeDeviceConfig;

// Global variables to allow us to do various waits.
BOOL g_bReplyRecieved;
DWORD g_dwRequestedID;
long g_lAsyncReply;
BOOL g_bCallStateReceived;

// Structures needed to handle special non-dialable characters.
#define g_sizeofNonDialable (sizeof(g_sNonDialable)/sizeof(g_sNonDialable[0]))

typedef struct {
LONG lError;
DWORD dwDevCapFlag;
LPSTR szToken;
LPSTR szMsg;
} NONDIALTOKENS;

NONDIALTOKENS g_sNonDialable[] = {
{LINEERR_DIALBILLING, LINEDEVCAPFLAGS_DIALBILLING, "$",
"Wait for the credit card bong tone" },
{LINEERR_DIALDIALTONE, LINEDEVCAPFLAGS_DIALDIALTONE, "W",
"Wait for the second dial tone" },
{LINEERR_DIALDIALTONE, LINEDEVCAPFLAGS_DIALDIALTONE, "w",
"Wait for the second dial tone" },
{LINEERR_DIALQUIET, LINEDEVCAPFLAGS_DIALQUIET, "@",
"Wait for the remote end to answer" },
{LINEERR_DIALPROMPT, 0, "?",
"Press OK when you are ready to continue dialing"},
};

// "Dial" dialog controls and their associated help page IDs
DWORD g_adwSampleMenuHelpIDs[] =
{
IDC_COUNTRYCODE , IDC_COUNTRYCODE,
IDC_STATICCOUNTRYCODE , IDC_COUNTRYCODE,
IDC_AREACODE , IDC_AREACODE,
IDC_STATICAREACODE , IDC_AREACODE,
IDC_PHONENUMBER , IDC_PHONENUMBER,
IDC_STATICPHONENUMBER , IDC_PHONENUMBER,
IDC_USEDIALINGRULES , IDC_USEDIALINGRULES,
IDC_LOCATION , IDC_LOCATION,
IDC_STATICLOCATION , IDC_LOCATION,
IDC_CALLINGCARD , IDC_CALLINGCARD,
IDC_STATICCALLINGCARD , IDC_CALLINGCARD,
IDC_DIALINGPROPERTIES , IDC_DIALINGPROPERTIES,
IDC_TAPILINE , IDC_TAPILINE,
IDC_STATICTAPILINE , IDC_TAPILINE,
IDC_CONFIGURELINE , IDC_CONFIGURELINE,
IDC_CANONICALNUMBER , IDC_CANONICALNUMBER,
IDC_STATICCANONICAL , IDC_CANONICALNUMBER,
IDC_DIALABLENUMBER , IDC_DIALABLENUMBER,
IDC_STATICDIALABLE , IDC_DIALABLENUMBER,
IDC_DISPLAYABLENUMBER , IDC_DISPLAYABLENUMBER,
IDC_STATICDISPLAYABLE , IDC_DISPLAYABLENUMBER,
IDC_DIAL , IDC_DIAL,
IDC_LINEICON , IDC_LINEICON,
//IDC_STATICWHERETODIAL , IDC_STATICWHERETODIAL,
//IDC_STATICHOWTODIAL , IDC_STATICHOWTODIAL,
//IDC_STATICCONNECTUSING , IDC_STATICCONNECTUSING,
//IDC_STATICPHONENUMBER , IDC_PHONENUMBER,
0,0
};

//**************************************************
// Prototypes for functions used only in this module.
//**************************************************

BOOL DialCallInParts (
LPLINEDEVCAPS lpLineDevCaps,
LPCSTR lpszAddress,
LPCSTR lpszDisplayableAddress);

LPLINECALLPARAMS CreateCallParams (
LPLINECALLPARAMS lpCallParams,
LPCSTR lpszDisplayableAddress);

DWORD I_lineNegotiateAPIVersion (
DWORD dwDeviceID);

LPVOID CheckAndReAllocBuffer(
LPVOID lpBuffer, size_t sizeBufferMinimum,
LPCSTR szApiPhrase);

LPLINEDEVCAPS I_lineGetDevCaps (
LPLINEDEVCAPS lpLineDevCaps,
DWORD dwDeviceID,
DWORD dwAPIVersion);

LPLINEADDRESSSTATUS I_lineGetAddressStatus (
LPLINEADDRESSSTATUS lpLineAddressStatus,
HLINE hLine,
DWORD dwAddressID);

LPLINETRANSLATEOUTPUT I_lineTranslateAddress (
LPLINETRANSLATEOUTPUT lpLineTranslateOutput,
DWORD dwDeviceID,
DWORD dwAPIVersion,
LPCSTR lpszDialAddress);

LPLINECALLSTATUS I_lineGetCallStatus (
LPLINECALLSTATUS lpLineCallStatus,
HCALL hCall);

LPLINEADDRESSCAPS I_lineGetAddressCaps (
LPLINEADDRESSCAPS lpLineAddressCaps,
DWORD dwDeviceID, DWORD dwAddressID,
DWORD dwAPIVersion, DWORD dwExtVersion);

long WaitForCallState (DWORD dwNewCallState);

long WaitForReply (long lRequestID);

void CALLBACK lineCallbackFunc(
DWORD hDevice, DWORD dwMsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3);

void DoLineReply(
DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3);
void DoLineClose(
DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3);
void DoLineDevState(
DWORD dwDevice, DWORD dwsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3);
void DoLineCallState(
DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3);
void DoLineCreate(
DWORD dwDevice, DWORD dwMessage, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3);

BOOL HandleLineErr(long lLineErr);

BOOL HandleIniFileCorrupt();
BOOL HandleNoDriver();
BOOL HandleNoDevicesInstalled();
BOOL HandleReInit();
BOOL HandleNoMultipleInstance();
BOOL HandleNoMem();
BOOL HandleOperationFailed();
BOOL HandleResourceUnavail();

BOOL LaunchModemControlPanelAdd();

void WarningBox(LPCSTR lpszMessage);

BOOL CALLBACK DialDialogProc(
HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

BOOL GetAddressToDial();

DWORD I_lineNegotiateLegacyAPIVersion(DWORD dwDeviceID);
long VerifyUsableLine(DWORD dwDeviceID);
void FillTAPILine(HWND hwndDlg);
BOOL VerifyAndWarnUsableLine(HWND hwndDlg);
void FillCountryCodeList(HWND hwndDlg, DWORD dwDefaultCountryID);
void FillLocationInfo(HWND hwndDlg, LPSTR lpszCurrentLocation,
LPDWORD lpdwCountryID, LPSTR lpszAreaCode);
void UseDialingRules(HWND hwndDlg);
void DisplayPhoneNumber(HWND hwndDlg);
void PreConfigureDevice(HWND hwndDlg, DWORD dwDeviceID);


//**************************************************
// Entry points from the UI
//**************************************************


//
// FUNCTION: BOOL InitializeTAPI(HWND)
//
// PURPOSE: Initializes TAPI
//
// PARAMETERS:
// hWndParent - Window to use as parent of any dialogs.
//
// RETURN VALUE:
// Always returns 0 - command handled.
//
// COMMENTS:
//
// This is the API that initializes the app with TAPI.
// If NULL is passed for the hWndParent, then its assumed
// that re-initialization has occurred and the previous hWnd
// is used.
//
//

BOOL InitializeTAPI(HWND hWndParent)
{
long lReturn;
BOOL bTryReInit = TRUE;

// If we're already initialized, then initialization succeeds.
if (g_hLineApp)
return TRUE;

// If we're in the middle of initializing, then fail, we're not done.
if (g_bInitializing)
return FALSE;

g_bInitializing = TRUE;

// Initialize TAPI
do
{
lReturn = lineInitialize(&g_hLineApp, hInst,
lineCallbackFunc, "TapiComm", &g_dwNumDevs);

// If we get this error, its because some other app has yet
// to respond to the REINIT message. Wait 5 seconds and try
// again. If it still doesn't respond, tell the user.
if (lReturn == LINEERR_REINIT)
{
if (bTryReInit)
{
MSG msg;
DWORD dwTimeStarted;

dwTimeStarted = GetTickCount();

while(GetTickCount() - dwTimeStarted < 5000)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

bTryReInit = FALSE;
continue;
}
else
{
MessageBox(g_hDlgParentWindow,
"A change to the system configuration requires that "
"all Telephony applications relinquish their use of "
"Telephony before any can progress. "
"Some have not yet done so."
,"Warning",MB_OK);
g_bInitializing = FALSE;
return FALSE;
}
}

if (lReturn == LINEERR_NODEVICE)
{
if (HandleNoDevicesInstalled())
continue;
else
{
OutputDebugString("No devices installed.\n");
g_bInitializing = FALSE;
return FALSE;
}
}

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineInitialize unhandled error: ");
g_bInitializing = FALSE;
return FALSE;
}
}
while(lReturn != SUCCESS);

// if hWndParent is a valid hWnd, we keep it as the parent for
// all dialogs.
if (IsWindow(hWndParent))
{
g_hDlgParentWindow = g_hWndMainWindow = hWndParent;
}
else
{
// Has the old g_hWndMainWindow gone away?
if (!IsWindow(g_hWndMainWindow))
{
OutputDebugString("Main window unavailable.\n");
g_hDlgParentWindow = g_hWndMainWindow = NULL;
}
}

g_hCall = NULL;
g_hLine = NULL;

OutputDebugString("Tapi initialized.\n");
g_bInitializing = FALSE;
return TRUE;
}


//
// FUNCTION: BOOL ShutdownTAPI()
//
// PURPOSE: Shuts down all use of TAPI
//
// PARAMETERS:
// None
//
// RETURN VALUE:
// True if TAPI successfully shut down.
//
// COMMENTS:
//
// If ShutdownTAPI fails, then its likely either a problem
// with the service provider (and might require a system
// reboot to correct) or the application ran out of memory.
//
//

BOOL ShutdownTAPI()
{
long lReturn;

// If we aren't initialized, then Shutdown is unnecessary.
if (g_hLineApp == NULL)
return TRUE;

// Prevent ShutdownTAPI re-entrancy problems.
if (g_bShuttingDown)
return TRUE;

g_bShuttingDown = TRUE;

HangupCall();

do
{
lReturn = lineShutdown(g_hLineApp);
if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn, "lineShutdown unhandled error: ");
break;
}
}
while(lReturn != SUCCESS);

g_bTapiInUse = FALSE;
g_bConnected = FALSE;
g_hLineApp = NULL;
g_hCall = NULL;
g_hLine = NULL;
g_bShuttingDown = FALSE;
OutputDebugString("TAPI uninitialized.\n");
return TRUE;
}



//
// FUNCTION: BOOL HangupCall()
//
// PURPOSE: Hangup the call in progress if it exists.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE if call hung up successfully.
//
// COMMENTS:
//
// If HangupCall fails, then its likely either a problem
// with the service provider (and might require a system
// reboot to correct) or the application ran out of memory.
//
//

BOOL HangupCall()
{
LPLINECALLSTATUS pLineCallStatus = NULL;
long lReturn;

// Prevent HangupCall re-entrancy problems.
if (g_bStoppingCall)
return TRUE;

// if the 'Call' dialog is up, dismiss it.
if (g_hDialog)
PostMessage(g_hDialog, WM_COMMAND, IDCANCEL, 0);

// If Tapi is not being used right now, then the call is hung up.
if (!g_bTapiInUse)
return TRUE;

g_bStoppingCall = TRUE;
OutputDebugString("Stopping Call in progress\n");

// Disable the 'hangup call' user interface.
EnableHangupCall(g_hWndMainWindow, FALSE);

// Stop any data communications on the comm port.
StopComm();

// If there is a call in progress, drop and deallocate it.
if (g_hCall)
{
// I_lineGetCallStatus returns a LocalAlloc()d buffer
pLineCallStatus = I_lineGetCallStatus(pLineCallStatus, g_hCall);
if (pLineCallStatus == NULL)
{
ShutdownTAPI();
g_bStoppingCall = FALSE;
return FALSE;
}

// Only drop the call when the line is not IDLE.
if (!((pLineCallStatus -> dwCallState) & LINECALLSTATE_IDLE))
{
do
{
lReturn = WaitForReply(lineDrop(g_hCall, NULL, 0));

if (lReturn == WAITERR_WAITTIMEDOUT)
{
OutputDebugString("Call timed out in WaitForReply.\n");
break;
}

if (lReturn == WAITERR_WAITABORTED)
{
OutputDebugString("lineDrop: WAITERR_WAITABORTED.\n");
break;
}

// Was the call already in IDLE?
if (lReturn == LINEERR_INVALCALLSTATE)
break;

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineDrop unhandled error: ");
break;
}
}
while(lReturn != SUCCESS);

// Wait for the dropped call to go IDLE before continuing.
lReturn = WaitForCallState(LINECALLSTATE_IDLE);

if (lReturn == WAITERR_WAITTIMEDOUT)
OutputDebugString("Call timed out waiting for IDLE state.\n");

if (lReturn == WAITERR_WAITABORTED)
OutputDebugString(
"WAITERR_WAITABORTED while waiting for IDLE state.\n");

OutputDebugString("Call Dropped.\n");
}

// The call is now idle. Deallocate it!
do
{
lReturn = lineDeallocateCall(g_hCall);
if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineDeallocateCall unhandled error: ");
break;
}
}
while(lReturn != SUCCESS);

OutputDebugString("Call Deallocated.\n");
}


// if we have a line open, close it.
if (g_hLine)
{
do
{
lReturn = lineClose(g_hLine);
if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineClose unhandled error: ");
break;
}
}
while(lReturn != SUCCESS);

OutputDebugString("Line Closed.\n");
}

// Call and Line are taken care of. Finish cleaning up.

// If there is device configuration information, free the memory.
if (g_lpDeviceConfig)
LocalFree(g_lpDeviceConfig);
g_lpDeviceConfig = NULL;

g_hCall = NULL;
g_hLine = NULL;
g_bConnected = FALSE;

g_bTapiInUse = FALSE;
g_bStoppingCall = FALSE; // allow HangupCall to be called again.
OutputDebugString("Call stopped\n");

// Update the user interface.
UpdateStatusBar("Ready to make a call.",1,0);
EnableMakeCall(g_hWndMainWindow, TRUE);

// Need to free LocalAlloc()d buffer returned from I_lineGetCallStatus
if (pLineCallStatus)
LocalFree(pLineCallStatus);

return TRUE;
}


//
// FUNCTION: PostHangupCall()
//
// PURPOSE: Posts a message to the main TAPI thread to hangup the call.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// TAPI is thread specific, meaning that only the thread that does the
// lineInitialize can get asynchronous messages through the callback.
// Since the HangupCall can potentially go into a loop waiting for
// specific events, any other threads that call HangupCall can cause
// timing confusion. Best to just have other threads 'ask' the main thread
// to hangup the call.
//

void PostHangupCall()
{
PostMessage(g_hWndMainWindow, WM_COMMAND, IDM_HANGUPCALL, 0);
}



//
// FUNCTION: DialCall()
//
// PURPOSE: Get a number from the user and dial it.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE if able to get a number, find a line, and dial successfully.
//
// COMMENTS:
//
// This function makes several assumptions:
// - The number dialed will always explicitly come from the user.
// - There will only be one outgoing address per line.
//

BOOL DialCall()
{
long lReturn;
LPLINEADDRESSSTATUS lpLineAddressStatus = NULL;
LPLINEDEVCAPS lpLineDevCaps = NULL;

if (g_bTapiInUse)
{
OutputDebugString("A call is already being handled\n");
return FALSE;
}

// If TAPI isn't initialized, its either because we couldn't initialize
// at startup (and this might have been corrected by now), or because
// a REINIT event was received. In either case, try to init now.

if (!g_hLineApp)
{
if (!InitializeTAPI(NULL))
return FALSE;
}

// If there are no line devices installed on the machine, lets give
// the user the opportunity to install one.
if (g_dwNumDevs < 1)
{
if (!HandleNoDevicesInstalled())
return FALSE;
}

// We now have a call active. Prevent future calls.
g_bTapiInUse = TRUE;
EnableMakeCall(g_hWndMainWindow, FALSE);


// Get a phone number from the user.
// Phone number will be placed in global variables if successful
if (!GetAddressToDial())
{
HangupCall();
goto DeleteBuffers;
}

// Negotiate the API version to use for this device.
g_dwAPIVersion = I_lineNegotiateAPIVersion(g_dwDeviceID);
if (g_dwAPIVersion == 0)
{
MessageBox(g_hDlgParentWindow,
"Line Version unsupported by this Sample",
"Unable to Use Line",MB_OK);
HangupCall();
goto DeleteBuffers;
}

// Need to check the DevCaps to make sure this line is usable.
// The 'Dial' dialog checks also, but better safe than sorry.
lpLineDevCaps = I_lineGetDevCaps(lpLineDevCaps,
g_dwDeviceID, g_dwAPIVersion);
if (lpLineDevCaps == NULL)
{
HangupCall();
MessageBox(g_hDlgParentWindow,
"Error on Requested line",
"Unable to Use Line",MB_OK);
goto DeleteBuffers;
}

if (!(lpLineDevCaps->dwBearerModes & LINEBEARERMODE_VOICE ))
{
HangupCall();
MessageBox(g_hDlgParentWindow,
"Error on Requested line",
"The selected line doesn't support VOICE capabilities",
MB_OK);
goto DeleteBuffers;
}

if (!(lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_DATAMODEM))
{
HangupCall();
MessageBox(g_hDlgParentWindow,
"Error on Requested line",
"The selected line doesn't support DATAMODEM capabilities",
MB_OK);
goto DeleteBuffers;
}

// Does this line have the capability to make calls?
// It is possible that some lines can't make outbound calls.
if (!(lpLineDevCaps->dwLineFeatures & LINEFEATURE_MAKECALL))
{
HangupCall();
MessageBox(g_hDlgParentWindow,
"Error on Requested line",
"The selected line doesn't support MAKECALL capabilities",
MB_OK);
goto DeleteBuffers;
}

// Open the Line for an outgoing DATAMODEM call.
do
{
lReturn = lineOpen(g_hLineApp, g_dwDeviceID, &g_hLine,
g_dwAPIVersion, 0, 0,
LINECALLPRIVILEGE_NONE, LINEMEDIAMODE_DATAMODEM,
0);

if(lReturn == LINEERR_ALLOCATED)
{
HangupCall();
MessageBox(g_hDlgParentWindow,
"Line is already in use by a non-TAPI application "
"or by another TAPI Service Provider.",
"Unable to Use Line",MB_OK);
goto DeleteBuffers;
}

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn, "lineOpen unhandled error: ");
MessageBox(g_hDlgParentWindow,
"Error on Requested line",
"Unable to Use Line",MB_OK);
HangupCall();
goto DeleteBuffers;
}
}
while(lReturn != SUCCESS);

// Tell the service provider that we want all notifications that
// have anything to do with this line.
do
{
// Set the messages we are interested in.

// Note that while most applications aren't really interested
// in dealing with all of the possible messages, its interesting
// to see which come through the callback for testing purposes.

lReturn = lineSetStatusMessages(g_hLine,
LINEDEVSTATE_OTHER |
LINEDEVSTATE_RINGING |
LINEDEVSTATE_CONNECTED | // Important state!
LINEDEVSTATE_DISCONNECTED | // Important state!
LINEDEVSTATE_MSGWAITON |
LINEDEVSTATE_MSGWAITOFF |
LINEDEVSTATE_INSERVICE |
LINEDEVSTATE_OUTOFSERVICE | // Important state!
LINEDEVSTATE_MAINTENANCE | // Important state!
LINEDEVSTATE_OPEN |
LINEDEVSTATE_CLOSE |
LINEDEVSTATE_NUMCALLS |
LINEDEVSTATE_NUMCOMPLETIONS |
LINEDEVSTATE_TERMINALS |
LINEDEVSTATE_ROAMMODE |
LINEDEVSTATE_BATTERY |
LINEDEVSTATE_SIGNAL |
LINEDEVSTATE_DEVSPECIFIC |
LINEDEVSTATE_REINIT | // Not allowed to disable this.
LINEDEVSTATE_LOCK |
LINEDEVSTATE_CAPSCHANGE |
LINEDEVSTATE_CONFIGCHANGE |
LINEDEVSTATE_COMPLCANCEL ,

LINEADDRESSSTATE_OTHER |
LINEADDRESSSTATE_DEVSPECIFIC|
LINEADDRESSSTATE_INUSEZERO |
LINEADDRESSSTATE_INUSEONE |
LINEADDRESSSTATE_INUSEMANY |
LINEADDRESSSTATE_NUMCALLS |
LINEADDRESSSTATE_FORWARD |
LINEADDRESSSTATE_TERMINALS |
LINEADDRESSSTATE_CAPSCHANGE);


if (HandleLineErr(lReturn))
continue;
else
{
// If we do get an unhandled problem, we don't care.
// We just won't get notifications.
OutputDebugLineError(lReturn,
"lineSetStatusMessages unhandled error: ");
break;
}
}
while(lReturn != SUCCESS);


// Get LineAddressStatus so we can make sure the line
// isn't already in use by a TAPI application.
lpLineAddressStatus =
I_lineGetAddressStatus(lpLineAddressStatus, g_hLine, 0);

if (lpLineAddressStatus == NULL)
{
HangupCall();
MessageBox(g_hDlgParentWindow,
"Error on Requested line",
"Unable to Use Line",MB_OK);
goto DeleteBuffers;
}

// MAKECALL will be set if there are any available call appearances
if ( ! ((lpLineAddressStatus -> dwAddressFeatures) &
LINEADDRFEATURE_MAKECALL) )

{ 
OutputDebugString("This line is not available to place a call.\n");
HangupCall();
MessageBox(g_hDlgParentWindow,
"Requested line is already in use",
"Unable to Use Line",MB_OK);
goto DeleteBuffers;
}

// If the line was configured in the 'Dial' dialog, then
// we need to actually complete the configuration.
if (g_lpDeviceConfig)
lineSetDevConfig(g_dwDeviceID, g_lpDeviceConfig,
g_dwSizeDeviceConfig, "comm/datamodem");

// Start dialing the number
if (DialCallInParts(lpLineDevCaps, g_szDialableAddress,
g_szDisplayableAddress))
{
OutputDebugString("DialCallInParts succeeded.\n");
}
else
{
OutputDebugString("DialCallInParts failed.\n");
HangupCall();
goto DeleteBuffers;
}

DeleteBuffers:

if (lpLineAddressStatus)
LocalFree(lpLineAddressStatus);
if (lpLineDevCaps)
LocalFree(lpLineDevCaps);
if (g_bTapiInUse)
EnableHangupCall(g_hWndMainWindow, TRUE);

return g_bTapiInUse;
}


//**************************************************
// These APIs are specific to this module
//**************************************************



//
// FUNCTION: DialCallInParts(LPLINEDEVCAPS, LPCSTR, LPCSTR)
//
// PURPOSE: Dials the call, handling special characters.
//
// PARAMETERS:
// lpLineDevCaps - LINEDEVCAPS for the line to be used.
// lpszAddress - Address to Dial.
// lpszDisplayableAddress - Displayable Address.
//
// RETURN VALUE:
// Returns TRUE if we successfully Dial.
//
// COMMENTS:
//
// This function dials the Address and handles any
// special characters in the address that the service provider
// can't handle. It requires input from the user to handle
// these characters; this can cause problems for fully automated
// dialing.
//
// Note that we can return TRUE, even if we don't reach a
// CONNECTED state. DIalCallInParts returns as soon as the
// Address is fully dialed or when an error occurs.
//
//

BOOL DialCallInParts(LPLINEDEVCAPS lpLineDevCaps,
LPCSTR lpszAddress, LPCSTR lpszDisplayableAddress)
{
LPLINECALLPARAMS lpCallParams = NULL;
LPLINEADDRESSCAPS lpAddressCaps = NULL;
LPLINECALLSTATUS lpLineCallStatus = NULL;

long lReturn;
int i;
DWORD dwDevCapFlags;
char szFilter[1+sizeof(g_sNonDialable)] = "";
BOOL bFirstDial = TRUE;

// Variables to handle Dialable Substring dialing.
LPSTR lpDS; // This is just so we can free lpszDialableSubstring later.
LPSTR lpszDialableSubstring;
int nAddressLength = 0;
int nCurrentAddress = 0;
char chUnhandledCharacter;

// Get the capabilities for the line device we're going to use.
lpAddressCaps = I_lineGetAddressCaps(lpAddressCaps,
g_dwDeviceID, 0, g_dwAPIVersion, 0);
if (lpAddressCaps == NULL)
return FALSE;

// Setup our CallParams for DATAMODEM settings.
lpCallParams = CreateCallParams (lpCallParams, lpszDisplayableAddress);
if (lpCallParams == NULL)
return FALSE;

// Determine which special characters the service provider
// does *not* handle so we can handle them manually.
// Keep list of unhandled characters in szFilter.

dwDevCapFlags = lpLineDevCaps -> dwDevCapFlags; // SP handled characters.
for (i = 0; i < g_sizeofNonDialable ; i++)
{
if ((dwDevCapFlags & g_sNonDialable[i].dwDevCapFlag) == 0)
{
strcat(szFilter, g_sNonDialable[i].szToken);
}
}

// szFilter now contains the set of tokens which delimit dialable substrings

// Setup the strings for substring dialing.

nAddressLength = strlen(lpszAddress);
lpDS = lpszDialableSubstring = (LPSTR) LocalAlloc(LPTR, nAddressLength + 1);
if (lpszDialableSubstring == NULL)
{
OutputDebugLastError(GetLastError(), "LocalAlloc failed: ");
HandleNoMem();
goto errExit;
}

// Lets start dialing substrings!
while (nCurrentAddress < nAddressLength)
{
retryAfterError:

// Find the next undialable character
i = strcspn(&lpszAddress[nCurrentAddress], szFilter);

// Was there one before the end of the Address string?
if (i + nCurrentAddress < nAddressLength)
{
// Make sure this device can handle partial dial.
if (! (lpAddressCaps -> dwAddrCapFlags &
LINEADDRCAPFLAGS_PARTIALDIAL))
{
MessageBox(g_hDlgParentWindow,
"This line doesn't support partial dialing.\n",
"Warning",MB_OK);
goto errExit;
}
// Remember what the unhandled character is so we can handle it.
chUnhandledCharacter = lpszAddress[nCurrentAddress+i];

// Copy the dialable string to the Substring.
memcpy(lpszDialableSubstring, &lpszAddress[nCurrentAddress], i);

// Terminate the substring with a ';' to signify the partial dial.
lpszDialableSubstring[i] = ';';
lpszDialableSubstring[i+1] = '\0';

// Increment the address for next iteration.
nCurrentAddress += i + 1;
}
else // No more partial dials. Dial the rest of the Address.
{
lpszDialableSubstring = (LPSTR) &lpszAddress[nCurrentAddress];
chUnhandledCharacter = 0;
nCurrentAddress = nAddressLength;
}

do
{
if (bFirstDial)
lReturn = WaitForReply(
lineMakeCall(g_hLine, &g_hCall, lpszDialableSubstring,
0, lpCallParams) );
else
lReturn = WaitForReply(
lineDial(g_hCall, lpszDialableSubstring, 0) );

switch(lReturn)
{
// We should not have received these errors because of the
// prefiltering strategy, but there may be some ill-behaved
// service providers which do not correctly set their
// devcapflags. Add the character corresponding to the error
// to the filter set and retry dialing.
//
case LINEERR_DIALBILLING:
case LINEERR_DIALDIALTONE:
case LINEERR_DIALQUIET:
case LINEERR_DIALPROMPT:
{
OutputDebugString("Service Provider incorrectly sets dwDevCapFlags\n");

for (i = 0; i < g_sizeofNonDialable; i++)
if (lReturn == g_sNonDialable[i].lError)
{
strcat(szFilter, g_sNonDialable[i].szToken);
}

goto retryAfterError;
}

case WAITERR_WAITABORTED:
OutputDebugString("While Dialing, WaitForReply aborted.\n");
goto errExit;

}

if (HandleLineErr(lReturn))
continue;
else
{
if (bFirstDial)
OutputDebugLineError(lReturn, "lineMakeCall unhandled error: ");
else
OutputDebugLineError(lReturn, "lineDial unhandled error: ");

goto errExit;
}

}
while (lReturn != SUCCESS);

bFirstDial = FALSE;

// The dial was successful; now handle characters the service
// provider didn't (if any).
if (chUnhandledCharacter)
{
LPSTR lpMsg = "";

// First, wait until we know we can continue dialing. While the
// last string is still pending to be dialed, we can't dial another.

while(TRUE)
{

lpLineCallStatus = I_lineGetCallStatus(lpLineCallStatus, g_hCall);
if (lpLineCallStatus == NULL)
goto errExit;

// Does CallStatus say we can dial now?
if ((lpLineCallStatus->dwCallFeatures) & LINECALLFEATURE_DIAL)
{
OutputDebugString("Ok to continue dialing.\n");
break;
}

// We can't dial yet, so wait for a CALLSTATE message
OutputDebugString("Waiting for dialing to be enabled.\n");

if (WaitForCallState(I_LINECALLSTATE_ANY) != SUCCESS)
goto errExit;
}

for (i = 0; i < g_sizeofNonDialable; i++)
if (chUnhandledCharacter == g_sNonDialable[i].szToken[0])
lpMsg = g_sNonDialable[i].szMsg;

MessageBox(g_hDlgParentWindow, lpMsg, "Dialing Paused", MB_OK);
}

} // continue dialing until we dial all Dialable Substrings.

LocalFree(lpCallParams);
LocalFree(lpDS);
LocalFree(lpAddressCaps);
if (lpLineCallStatus)
LocalFree(lpLineCallStatus);

return TRUE;

errExit:
// if lineMakeCall has already been successfully called, there's a call in progress.
// let the invoking routine shut down the call.
// if the invoker did not clean up the call, it should be done here.

if (lpLineCallStatus)
LocalFree(lpLineCallStatus);
if (lpDS)
LocalFree(lpDS);
if (lpCallParams)
LocalFree(lpCallParams);
if (lpAddressCaps)
LocalFree(lpAddressCaps);

return FALSE;
}


//
// FUNCTION: CreateCallParams(LPLINECALLPARAMS, LPCSTR)
//
// PURPOSE: Allocates and fills a LINECALLPARAMS structure
//
// PARAMETERS:
// lpCallParams -
// lpszDisplayableAddress -
//
// RETURN VALUE:
// Returns a LPLINECALLPARAMS ready to use for dialing DATAMODEM calls.
// Returns NULL if unable to allocate the structure.
//
// COMMENTS:
//
// If a non-NULL lpCallParams is passed in, it must have been allocated
// with LocalAlloc, and can potentially be freed and reallocated. It must
// also have the dwTotalSize field correctly set.
//
//

LPLINECALLPARAMS CreateCallParams (
LPLINECALLPARAMS lpCallParams, LPCSTR lpszDisplayableAddress)
{
size_t sizeDisplayableAddress;

if (lpszDisplayableAddress == NULL)
lpszDisplayableAddress = "";

sizeDisplayableAddress = strlen(lpszDisplayableAddress) + 1;

lpCallParams = (LPLINECALLPARAMS) CheckAndReAllocBuffer(
(LPVOID) lpCallParams,
sizeof(LINECALLPARAMS) + sizeDisplayableAddress,
"CreateCallParams: ");

if (lpCallParams == NULL)
return NULL;

// This is where we configure the line for DATAMODEM usage.
lpCallParams -> dwBearerMode = LINEBEARERMODE_VOICE;
lpCallParams -> dwMediaMode = LINEMEDIAMODE_DATAMODEM;

// This specifies that we want to use only IDLE calls and
// don't want to cut into a call that might not be IDLE (ie, in use).
lpCallParams -> dwCallParamFlags = LINECALLPARAMFLAGS_IDLE;

// if there are multiple addresses on line, use first anyway.
// It will take a more complex application than a simple tty app
// to use multiple addresses on a line anyway.
lpCallParams -> dwAddressMode = LINEADDRESSMODE_ADDRESSID;
lpCallParams -> dwAddressID = 0;

// Since we don't know where we originated, leave these blank.
lpCallParams -> dwOrigAddressSize = 0;
lpCallParams -> dwOrigAddressOffset = 0;

// Unimodem ignores these values.
(lpCallParams -> DialParams) . dwDialSpeed = 0;
(lpCallParams -> DialParams) . dwDigitDuration = 0;
(lpCallParams -> DialParams) . dwDialPause = 0;
(lpCallParams -> DialParams) . dwWaitForDialtone = 0;

// Address we are dialing.
lpCallParams -> dwDisplayableAddressOffset = sizeof(LINECALLPARAMS);
lpCallParams -> dwDisplayableAddressSize = sizeDisplayableAddress;
strcpy((LPSTR)lpCallParams + sizeof(LINECALLPARAMS),
lpszDisplayableAddress);

return lpCallParams;
}


//
// FUNCTION: long WaitForReply(long)
//
// PURPOSE: Resynchronize by waiting for a LINE_REPLY
//
// PARAMETERS:
// lRequestID - The asynchronous request ID that we're
// on a LINE_REPLY for.
//
// RETURN VALUE:
// - 0 if LINE_REPLY responded with a success.
// - LINEERR constant if LINE_REPLY responded with a LINEERR
// - 1 if the line was shut down before LINE_REPLY is received.
//
// COMMENTS:
//
// This function allows us to resynchronize an asynchronous
// TAPI line call by waiting for the LINE_REPLY message. It
// waits until a LINE_REPLY is received or the line is shut down.
//
// Note that this could cause re-entrancy problems as
// well as mess with any message preprocessing that might
// occur on this thread (such as TranslateAccelerator).
//
// This function should to be called from the thread that did
// lineInitialize, or the PeekMessage is on the wrong thread
// and the synchronization is not guaranteed to work. Also note
// that if another PeekMessage loop is entered while waiting,
// this could also cause synchronization problems.
//
// One more note. This function can potentially be re-entered
// if the call is dropped for any reason while waiting. If this
// happens, just drop out and assume the wait has been canceled.
// This is signaled by setting bReentered to FALSE when the function
// is entered and TRUE when it is left. If bReentered is ever TRUE
// during the function, then the function was re-entered.
//
// This function times out and returns WAITERR_WAITTIMEDOUT
// after WAITTIMEOUT milliseconds have elapsed.
//
//


long WaitForReply (long lRequestID)
{
static BOOL bReentered;
bReentered = FALSE;

if (lRequestID > SUCCESS)
{
MSG msg;
DWORD dwTimeStarted;

g_bReplyRecieved = FALSE;
g_dwRequestedID = (DWORD) lRequestID;

// Initializing this just in case there is a bug
// that sets g_bReplyRecieved without setting the reply value.
g_lAsyncReply = LINEERR_OPERATIONFAILED;

dwTimeStarted = GetTickCount();

while(!g_bReplyRecieved)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

// This should only occur if the line is shut down while waiting.
if (!g_bTapiInUse || bReentered)
{
bReentered = TRUE;
return WAITERR_WAITABORTED;
}

// Its a really bad idea to timeout a wait for a LINE_REPLY.
// If we are execting a LINE_REPLY, we should wait till we get
// it; it might take a long time to dial (for example).

// If 5 seconds go by without a reply, it might be a good idea
// to display a dialog box to tell the user that a
// wait is in progress and to give the user the capability to
// abort the wait.
}

bReentered = TRUE;
return g_lAsyncReply;
}

bReentered = TRUE;
return lRequestID;
}


//
// FUNCTION: long WaitForCallState(DWORD)
//
// PURPOSE: Wait for the line to reach a specific CallState.
//
// PARAMETERS:
// dwDesiredCallState - specific CallState to wait for.
//
// RETURN VALUE:
// Returns 0 (SUCCESS) when we reach the Desired CallState.
// Returns WAITERR_WAITTIMEDOUT if timed out.
// Returns WAITERR_WAITABORTED if call was closed while waiting.
//
// COMMENTS:
//
// This function allows us to synchronously wait for a line
// to reach a specific LINESTATE or until the line is shut down.
//
// Note that this could cause re-entrancy problems as
// well as mess with any message preprocessing that might
// occur on this thread (such as TranslateAccelerator).
//
// One more note. This function can potentially be re-entered
// if the call is dropped for any reason while waiting. If this
// happens, just drop out and assume the wait has been canceled.
// This is signaled by setting bReentered to FALSE when the function
// is entered and TRUE when it is left. If bReentered is ever TRUE
// during the function, then the function was re-entered.
//
// This function should to be called from the thread that did
// lineInitialize, or the PeekMessage is on the wrong thread
// and the synchronization is not guaranteed to work. Also note
// that if another PeekMessage loop is entered while waiting,
// this could also cause synchronization problems.
//
// If the constant value I_LINECALLSTATE_ANY is used for the
// dwDesiredCallState, then WaitForCallState will return SUCCESS
// upon receiving any CALLSTATE messages.
//
//
//

long WaitForCallState(DWORD dwDesiredCallState)
{
MSG msg;
DWORD dwTimeStarted;
static BOOL bReentered;

bReentered = FALSE;

dwTimeStarted = GetTickCount();

g_bCallStateReceived = FALSE;

while ((dwDesiredCallState == I_LINECALLSTATE_ANY) ||
(g_dwCallState != dwDesiredCallState))
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

// If we are waiting for any call state and get one, succeed.
if ((dwDesiredCallState == I_LINECALLSTATE_ANY) &&
g_bCallStateReceived)
{
break;
}

// This should only occur if the line is shut down while waiting.
if (!g_bTapiInUse || bReentered)
{
bReentered = TRUE;
OutputDebugString("WAITABORTED\n");
return WAITERR_WAITABORTED;
}

// If we don't get the reply in a reasonable time, we time out.
if (GetTickCount() - dwTimeStarted > WAITTIMEOUT)
{
bReentered = TRUE;
OutputDebugString("WAITTIMEDOUT\n");
return WAITERR_WAITTIMEDOUT;
}

}

bReentered = TRUE;
return SUCCESS;
}

//**************************************************
// lineCallback Function and Handlers.
//**************************************************


//
// FUNCTION: lineCallbackFunc(..)
//
// PURPOSE: Receive asynchronous TAPI events
//
// PARAMETERS:
// dwDevice - Device associated with the event, if any
// dwMsg - TAPI event that occurred.
// dwCallbackInstance - User defined data supplied when opening the line.
// dwParam1 - dwMsg specific information
// dwParam2 - dwMsg specific information
// dwParam3 - dwMsg specific information
//
// RETURN VALUE:
// none
//
// COMMENTS:
// This is the function where all asynchronous events will come.
// Almost all events will be specific to an open line, but a few
// will be general TAPI events (such as LINE_REINIT).
//
// Its important to note that this callback will *ALWAYS* be
// called in the context of the thread that does the lineInitialize.
// Even if another thread (such as the COMM threads) calls the API
// that would result in the callback being called, it will be called
// in the context of the main thread (since in this sample, the main
// thread does the lineInitialize).
//
//


void CALLBACK lineCallbackFunc(
DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{

OutputDebugLineCallback(
dwDevice, dwMsg, dwCallbackInstance,
dwParam1, dwParam2, dwParam3);

// All we do is dispatch the dwMsg to the correct handler.
switch(dwMsg)
{
case LINE_CALLSTATE:
DoLineCallState(dwDevice, dwMsg, dwCallbackInstance,
dwParam1, dwParam2, dwParam3);
break;

case LINE_CLOSE:
DoLineClose(dwDevice, dwMsg, dwCallbackInstance,
dwParam1, dwParam2, dwParam3);
break;

case LINE_LINEDEVSTATE:
DoLineDevState(dwDevice, dwMsg, dwCallbackInstance,
dwParam1, dwParam2, dwParam3);
break;

case LINE_REPLY:
DoLineReply(dwDevice, dwMsg, dwCallbackInstance,
dwParam1, dwParam2, dwParam3);
break;

case LINE_CREATE:
DoLineCreate(dwDevice, dwMsg, dwCallbackInstance,
dwParam1, dwParam2, dwParam3);
break;

default:
OutputDebugString("lineCallbackFunc message ignored\n");
break;

}

return;

}


//
// FUNCTION: DoLineReply(..)
//
// PURPOSE: Handle LINE_REPLY asynchronous messages.
//
// PARAMETERS:
// dwDevice - Line Handle associated with this LINE_REPLY.
// dwMsg - Should always be LINE_REPLY.
// dwCallbackInstance - Unused by this sample.
// dwParam1 - Asynchronous request ID.
// dwParam2 - success or LINEERR error value.
// dwParam3 - Unused.
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// All line API calls that return an asynchronous request ID
// will eventually cause a LINE_REPLY message. Handle it.
//
// This sample assumes only one call at time, and that we wait
// for a LINE_REPLY before making any other line API calls.
//
// The only exception to the above is that we might shut down
// the line before receiving a LINE_REPLY.
//
//

void DoLineReply(
DWORD dwDevice, DWORD dwMessage, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
if ((long) dwParam2 != SUCCESS)
OutputDebugLineError((long) dwParam2, "LINE_REPLY error: ");
else
OutputDebugString("LINE_REPLY: successfully replied.\n");

// If we are currently waiting for this async Request ID
// then set the global variables to acknowledge it.
if (g_dwRequestedID == dwParam1)
{
g_bReplyRecieved = TRUE;
g_lAsyncReply = (long) dwParam2;
}
}


//
// FUNCTION: DoLineClose(..)
//
// PURPOSE: Handle LINE_CLOSE asynchronous messages.
//
// PARAMETERS:
// dwDevice - Line Handle that was closed.
// dwMsg - Should always be LINE_CLOSE.
// dwCallbackInstance - Unused by this sample.
// dwParam1 - Unused.
// dwParam2 - Unused.
// dwParam3 - Unused.
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// This message is sent when something outside our app shuts
// down a line in use.
//
// The hLine (and any hCall on this line) are no longer valid.
//
//

void DoLineClose(
DWORD dwDevice, DWORD dwMessage, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
// Line has been shut down. Clean up our internal variables.
g_hLine = NULL;
g_hCall = NULL;
UpdateStatusBar("Call was shut down.",1,0);
MessageBox(g_hDlgParentWindow,
"Call was shut down.","Warning",MB_OK);
HangupCall();
}


//
// FUNCTION: DoLineDevState(..)
//
// PURPOSE: Handle LINE_LINEDEVSTATE asynchronous messages.
//
// PARAMETERS:
// dwDevice - Line Handle that was closed.
// dwMsg - Should always be LINE_LINEDEVSTATE.
// dwCallbackInstance - Unused by this sample.
// dwParam1 - LINEDEVSTATE constant.
// dwParam2 - Depends on dwParam1.
// dwParam3 - Depends on dwParam1.
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// The LINE_LINEDEVSTATE message is received if the state of the line
// changes. Examples are RINGING, MAINTENANCE, MSGWAITON. Very few of
// these are relevant to this sample.
//
// Assuming that any LINEDEVSTATE that removes the line from use by TAPI
// will also send a LINE_CLOSE message.
//
//

void DoLineDevState(
DWORD dwDevice, DWORD dwMessage, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
switch(dwParam1)
{
case LINEDEVSTATE_RINGING:
UpdateStatusBar("Line Ringing",1,0);
OutputDebugString("Line Ringing.\n");
break;

case LINEDEVSTATE_REINIT:
// This is an important case! Usually means that a service provider
// has changed in such a way that requires TAPI to REINIT.
// Note that there are both 'soft' REINITs and 'hard' REINITs.
// Soft REINITs don't actually require a full shutdown but is instead
// just an informational change that historically required a REINIT
// to force the application to deal with. TAPI API Version 1.3 apps
// will still need to do a full REINIT for both hard and soft REINITs.

switch(dwParam2)
{
// This is the hard REINIT. No reason given, just REINIT.
// TAPI is waiting for everyone to shutdown.
// Our response is to immediately shutdown any calls,
// shutdown our use of TAPI and notify the user.
case 0:
ShutdownTAPI();
WarningBox("Tapi line configuration has changed.");
break;

case LINE_CREATE:
OutputDebugString("Soft REINIT: LINE_CREATE.\n");
DoLineCreate(dwDevice, dwParam2, dwCallbackInstance,
dwParam3, 0, 0);
break;

case LINE_LINEDEVSTATE:
OutputDebugString("Soft REINIT: LINE_LINEDEVSTATE.\n");
DoLineDevState(dwDevice, dwParam2, dwCallbackInstance,
dwParam3, 0, 0);
break;

// There might be other reasons to send a soft reinit.
// No need to to shutdown for these.
default:
OutputDebugString("Ignoring soft REINIT\n");
break;
}
break;

case LINEDEVSTATE_OUTOFSERVICE:
WarningBox("Line selected is now Out of Service.");
HangupCall();
break;

case LINEDEVSTATE_DISCONNECTED:
WarningBox("Line selected is now disconnected.");
HangupCall();
break;

case LINEDEVSTATE_MAINTENANCE:
WarningBox("Line selected is now out for maintenance.");
HangupCall();
break;

case LINEDEVSTATE_TRANSLATECHANGE:
if (g_hDialog)
PostMessage(g_hDialog, WM_COMMAND, IDC_CONFIGURATIONCHANGED, 0);
break;

case LINEDEVSTATE_REMOVED:
OutputDebugString("A Line device has been removed;"
" no action taken.\n");
break;

default:
OutputDebugString("Unhandled LINEDEVSTATE message\n");
}
}


//
// FUNCTION: DoLineCreate(..)
//
// PURPOSE: Handle LINE_LINECREATE asynchronous messages.
//
// PARAMETERS:
// dwDevice - Unused.
// dwMsg - Should always be LINE_CREATE.
// dwCallbackInstance - Unused.
// dwParam1 - dwDeviceID of new Line created.
// dwParam2 - Unused.
// dwParam3 - Unused.
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// This message is new for Windows 95. It is sent when a new line is
// added to the system. This allows us to handle new lines without having
// to REINIT. This allows for much more graceful Plug and Play.
//
// This sample just changes the number of devices available and can use
// it next time a call is made. It also tells the "Dial" dialog.
//
//

void DoLineCreate(
DWORD dwDevice, DWORD dwMessage, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
// dwParam1 is the Device ID of the new line.
// Add one to get the number of total devices.
if (g_dwNumDevs <= dwParam1)
g_dwNumDevs = dwParam1+1;
if (g_hDialog)
PostMessage(g_hDialog, WM_COMMAND, IDC_LINECREATE, 0);

}


//
// FUNCTION: DoLineCallState(..)
//
// PURPOSE: Handle LINE_CALLSTATE asynchronous messages.
//
// PARAMETERS:
// dwDevice - Handle to Call who's state is changing.
// dwMsg - Should always be LINE_CALLSTATE.
// dwCallbackInstance - Unused by this sample.
// dwParam1 - LINECALLSTATE constant specifying state change.

//    dwParam2  - Specific to dwParam1. 
// dwParam3 - LINECALLPRIVILEGE change, if any.
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// This message is received whenever a call changes state. Lots of
// things we do, ranging from notifying the user to closing the line
// to actually connecting to the target of our phone call.
//
// What we do is usually obvious based on the call state change.
//

void DoLineCallState(
DWORD dwDevice, DWORD dwMessage, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{

// Error if this CALLSTATE doesn't apply to our call in progress.
if ((HCALL) dwDevice != g_hCall)
{
OutputDebugPrintf("LINE_CALLSTATE: Unknown device ID '0x%lx'.",
dwDevice);
return;
}

// This sets the global g_dwCallState variable so if we are waiting
// for a specific call state change, we will know when it happens.
g_dwCallState = dwParam1;
g_bCallStateReceived = TRUE;

// dwParam3 contains changes to LINECALLPRIVILEGE, if there are any.
switch (dwParam3)
{
case 0:
break; // no change to call state

// close line if we are made monitor. Shouldn't happen!
case LINECALLPRIVILEGE_MONITOR:
OutputDebugString("line given monitor privilege; closing\n");
HangupCall();
return;

// close line if we are made owner. Shouldn't happen!
case LINECALLPRIVILEGE_OWNER:
OutputDebugString("line given owner privilege; closing\n");
HangupCall();
break;

default: // Shouldn't happen! All cases handled.
OutputDebugString("Unknown LINECALLPRIVILEGE message: closing\n");
HangupCall();
return;
}

// dwParam1 is the specific CALLSTATE change that is occurring.
switch (dwParam1)
{
case LINECALLSTATE_DIALTONE:
UpdateStatusBar("Dial Tone",1,0);
OutputDebugString("Dial Tone\n");
break;

case LINECALLSTATE_DIALING:
UpdateStatusBar("Dialing Call",1,0);
OutputDebugString("Dialing\n");
break;

case LINECALLSTATE_PROCEEDING:
UpdateStatusBar("Call is Proceeding",1,0);
OutputDebugString("Proceeding\n");
break;

case LINECALLSTATE_RINGBACK:
UpdateStatusBar("RingBack",1,0);
OutputDebugString("RingBack\n");
break;

case LINECALLSTATE_BUSY:
UpdateStatusBar("Line is busy",1,0);
OutputDebugString("Line busy, shutting down\n");
HangupCall();
break;

case LINECALLSTATE_IDLE:
UpdateStatusBar("Line is idle",1,0);
OutputDebugString("Line idle\n");
HangupCall();
break;

case LINECALLSTATE_SPECIALINFO:
UpdateStatusBar(
"Special Info, probably couldn't dial number",1,0);
OutputDebugString(
"Special Info, probably couldn't dial number\n");
HangupCall();
break;

case LINECALLSTATE_DISCONNECTED:
{
LPSTR pszReasonDisconnected;

switch (dwParam2)
{
case LINEDISCONNECTMODE_NORMAL:
pszReasonDisconnected = "Remote Party Disconnected";
break;

case LINEDISCONNECTMODE_UNKNOWN:
pszReasonDisconnected = "Disconnected: Unknown reason";
break;

case LINEDISCONNECTMODE_REJECT:
pszReasonDisconnected = "Remote Party rejected call";
break;

case LINEDISCONNECTMODE_PICKUP:
pszReasonDisconnected =
"Disconnected: Local phone picked up";
break;

case LINEDISCONNECTMODE_FORWARDED:
pszReasonDisconnected = "Disconnected: Forwarded";
break;

case LINEDISCONNECTMODE_BUSY:
pszReasonDisconnected = "Disconnected: Busy";
break;

case LINEDISCONNECTMODE_NOANSWER:
pszReasonDisconnected = "Disconnected: No Answer";
break;

case LINEDISCONNECTMODE_BADADDRESS:
pszReasonDisconnected = "Disconnected: Bad Address";
break;

case LINEDISCONNECTMODE_UNREACHABLE:
pszReasonDisconnected = "Disconnected: Unreachable";
break;

case LINEDISCONNECTMODE_CONGESTION:
pszReasonDisconnected = "Disconnected: Congestion";
break;

case LINEDISCONNECTMODE_INCOMPATIBLE:
pszReasonDisconnected = "Disconnected: Incompatible";
break;

case LINEDISCONNECTMODE_UNAVAIL:
pszReasonDisconnected = "Disconnected: Unavail";
break;

case LINEDISCONNECTMODE_NODIALTONE:
pszReasonDisconnected = "Disconnected: No Dial Tone";
break;

default:
pszReasonDisconnected =
"Disconnected: LINECALLSTATE; Bad Reason";
break;

}

UpdateStatusBar(pszReasonDisconnected,1,0);
OutputDebugString(pszReasonDisconnected);
OutputDebugString("\n");
HangupCall();
break;
}


case LINECALLSTATE_CONNECTED: // CONNECTED!!!
{
LPVARSTRING lpVarString = NULL;
DWORD dwSizeofVarString = sizeof(VARSTRING) + 1024;
HANDLE hCommFile = NULL;
long lReturn;

// Very first, make sure this isn't a duplicated message.
// A CALLSTATE message can be sent whenever there is a
// change to the capabilities of a line, meaning that it is
// possible to receive multiple CONNECTED messages per call.
// The CONNECTED CALLSTATE message is the only one in TapiComm
// where it would cause problems if it where sent more
// than once.

if (g_bConnected)
break;

g_bConnected = TRUE;

// Get the handle to the comm port from the driver so we can start
// communicating. This is returned in a LPVARSTRING structure.
do
{
// Allocate the VARSTRING structure
lpVarString = CheckAndReAllocBuffer((LPVOID) lpVarString,
dwSizeofVarString,"lineGetID: ");

if (lpVarString == NULL)
goto ErrorConnecting;

// Fill the VARSTRING structure
lReturn = lineGetID(0, 0, g_hCall, LINECALLSELECT_CALL,
lpVarString, "comm/datamodem");

if (HandleLineErr(lReturn))
; // Still need to check if structure was big enough.
else
{
OutputDebugLineError(lReturn,
"lineGetID unhandled error: ");
goto ErrorConnecting;
}

// If the VARSTRING wasn't big enough, loop again.
if ((lpVarString -> dwNeededSize) > (lpVarString -> dwTotalSize))
{
dwSizeofVarString = lpVarString -> dwNeededSize;
lReturn = -1; // Lets loop again.
}
}
while(lReturn != SUCCESS);

OutputDebugString("Connected! Starting communications!\n");

// Again, the handle to the comm port is contained in a
// LPVARSTRING structure. Thus, the handle is the very first
// thing after the end of the structure. Note that the name of
// the comm port is right after the handle, but I don't want it.
hCommFile =
*((LPHANDLE)((LPBYTE)lpVarString +
lpVarString -> dwStringOffset));

// Started communications!
if (StartComm(hCommFile))
{
char szBuff[300];

wsprintf(szBuff,"Connected to '%s'",g_szDisplayableAddress);
UpdateStatusBar(szBuff, 1, 0);

LocalFree(lpVarString);
break;
}

// Couldn't start communications. Clean up instead.
ErrorConnecting:

// Its very important that we close all Win32 handles.
// The CommCode module is responsible for closing the hCommFile
// handle if it succeeds in starting communications.
if (hCommFile)
CloseHandle(hCommFile);

HangupCall();
{
char szBuff[300];
wsprintf(szBuff,"Failed to Connect to '%s'",
g_szDisplayableAddress);
UpdateStatusBar(szBuff, 1, 0);
}

if (lpVarString)
LocalFree(lpVarString);

break;
}

default:
OutputDebugString("Unhandled LINECALLSTATE message\n");
break;
}
}

//**************************************************
// line API Wrapper Functions.
//**************************************************


//
// FUNCTION: LPVOID CheckAndReAllocBuffer(LPVOID, size_t, LPCSTR)
//
// PURPOSE: Checks and ReAllocates a buffer if necessary.
//
// PARAMETERS:
// lpBuffer - Pointer to buffer to be checked. Can be NULL.
// sizeBufferMinimum - Minimum size that lpBuffer should be.
// szApiPhrase - Phrase to print if an error occurs.
//
// RETURN VALUE:
// Returns a pointer to a valid buffer that is guarenteed to be
// at least sizeBufferMinimum size.
// Returns NULL if an error occured.
//
// COMMENTS:
//
// This function is a helper function intended to make all of the
// line API Wrapper Functions much simplier. It allocates (or
// reallocates) a buffer of the requested size.
//
// The returned pointer has been allocated with LocalAlloc,
// so LocalFree has to be called on it when you're finished with it,
// or there will be a memory leak.
//
// Similarly, if a pointer is passed in, it *must* have been allocated
// with LocalAlloc and it could potentially be LocalFree()d.
//
// If lpBuffer == NULL, then a new buffer is allocated. It is
// normal to pass in NULL for this parameter the first time and only
// pass in a pointer if the buffer needs to be reallocated.
//
// szApiPhrase is used only for debugging purposes.
//
// It is assumed that the buffer returned from this function will be used
// to contain a variable sized structure. Thus, the dwTotalSize field
// is always filled in before returning the pointer.
//
//

LPVOID CheckAndReAllocBuffer(
LPVOID lpBuffer, size_t sizeBufferMinimum, LPCSTR szApiPhrase)
{
size_t sizeBuffer;

if (lpBuffer == NULL) // Allocate the buffer if necessary.
{
sizeBuffer = sizeBufferMinimum;
lpBuffer = (LPVOID) LocalAlloc(LPTR, sizeBuffer);

if (lpBuffer == NULL)
{
OutputDebugString(szApiPhrase);
OutputDebugLastError(GetLastError(),"LocalAlloc : ");
HandleNoMem();
return NULL;
}
}
else // If the structure already exists, make sure its good.
{
sizeBuffer = LocalSize((HLOCAL) lpBuffer);

if (sizeBuffer == 0) // Bad pointer?
{
OutputDebugString(szApiPhrase);
OutputDebugLastError(GetLastError(),"LocalSize : ");
return NULL;
}

// Was the buffer big enough for the structure?
if (sizeBuffer < sizeBufferMinimum)
{
OutputDebugString(szApiPhrase);
OutputDebugString("Reallocating structure\n");
LocalFree(lpBuffer);
return CheckAndReAllocBuffer(NULL, sizeBufferMinimum, szApiPhrase);
}

// Lets zero the buffer out.
memset(lpBuffer, 0, sizeBuffer);
}

((LPVARSTRING) lpBuffer ) -> dwTotalSize = (DWORD) sizeBuffer;
return lpBuffer;
}



//
// FUNCTION: DWORD I_lineNegotiateAPIVersion(DWORD)
//
// PURPOSE: Negotiate an API Version to use for a specific device.
//
// PARAMETERS:
// dwDeviceID - device to negotiate an API Version for.
//
// RETURN VALUE:
// Returns the API Version to use for this line if successful.
// Returns 0 if negotiations fall through.
//
// COMMENTS:
//
// This wrapper function not only negotiates the API, but handles
// LINEERR errors that can occur while negotiating.
//
//

DWORD I_lineNegotiateAPIVersion(DWORD dwDeviceID)
{
LINEEXTENSIONID LineExtensionID;
long lReturn;
DWORD dwLocalAPIVersion;

do
{
lReturn = lineNegotiateAPIVersion(g_hLineApp, dwDeviceID,
SAMPLE_TAPI_VERSION, SAMPLE_TAPI_VERSION,
&dwLocalAPIVersion, &LineExtensionID);

if (lReturn == LINEERR_INCOMPATIBLEAPIVERSION)
{
OutputDebugString(
"lineNegotiateAPIVersion, INCOMPATIBLEAPIVERSION.\n");
return 0;
}

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineNegotiateAPIVersion unhandled error: ");
return 0;
}
}
while(lReturn != SUCCESS);

return dwLocalAPIVersion;
}


//
// FUNCTION: I_lineGetDevCaps(LPLINEDEVCAPS, DWORD , DWORD)
//
// PURPOSE: Retrieve a LINEDEVCAPS structure for the specified line.
//
// PARAMETERS:
// lpLineDevCaps - Pointer to a LINEDEVCAPS structure to use.
// dwDeviceID - device to get the DevCaps for.
// dwAPIVersion - API Version to use while getting DevCaps.
//
// RETURN VALUE:
// Returns a pointer to a LINEDEVCAPS structure if successful.
// Returns NULL if unsuccessful.
//
// COMMENTS:
//
// This function is a wrapper around lineGetDevCaps to make it easy
// to handle the variable sized structure and any errors received.
//
// The returned structure has been allocated with LocalAlloc,
// so LocalFree has to be called on it when you're finished with it,
// or there will be a memory leak.
//
// Similarly, if a lpLineDevCaps structure is passed in, it *must*
// have been allocated with LocalAlloc and it could potentially be
// LocalFree()d.
//
// If lpLineDevCaps == NULL, then a new structure is allocated. It is
// normal to pass in NULL for this parameter unless you want to use a
// lpLineDevCaps that has been returned by a previous I_lineGetDevCaps
// call.
//
//

LPLINEDEVCAPS I_lineGetDevCaps(
LPLINEDEVCAPS lpLineDevCaps,
DWORD dwDeviceID, DWORD dwAPIVersion)
{
size_t sizeofLineDevCaps = sizeof(LINEDEVCAPS) + 1024;
long lReturn;

// Continue this loop until the structure is big enough.
while(TRUE)
{
// Make sure the buffer exists, is valid and big enough.
lpLineDevCaps =
(LPLINEDEVCAPS) CheckAndReAllocBuffer(
(LPVOID) lpLineDevCaps, // Pointer to existing buffer, if any
sizeofLineDevCaps, // Minimum size the buffer should be
"lineGetDevCaps"); // Phrase to tag errors, if any.

if (lpLineDevCaps == NULL)
return NULL;

// Make the call to fill the structure.
do
{
lReturn =
lineGetDevCaps(g_hLineApp,
dwDeviceID, dwAPIVersion, 0, lpLineDevCaps);

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineGetDevCaps unhandled error: ");
LocalFree(lpLineDevCaps);
return NULL;
}
}
while (lReturn != SUCCESS);

// If the buffer was big enough, then succeed.
if ((lpLineDevCaps -> dwNeededSize) <= (lpLineDevCaps -> dwTotalSize))
return lpLineDevCaps;

// Buffer wasn't big enough. Make it bigger and try again.
sizeofLineDevCaps = lpLineDevCaps -> dwNeededSize;
}
}


//
// FUNCTION: I_lineGetAddressStatus(LPLINEADDRESSSTATUS, HLINE, DWORD)
//
// PURPOSE: Retrieve a LINEADDRESSSTATUS structure for the specified line.


//
// PARAMETERS:
// lpLineAddressStatus - Pointer to a LINEADDRESSSTATUS structure to use.
// hLine - Handle of line to get the AddressStatus of.
// dwAddressID - Address ID on the hLine to be used.
//
// RETURN VALUE:
// Returns a pointer to a LINEADDRESSSTATUS structure if successful.
// Returns NULL if unsuccessful.
//
// COMMENTS:
//
// This function is a wrapper around lineGetAddressStatus to make it easy
// to handle the variable sized structure and any errors received.
//
// The returned structure has been allocated with LocalAlloc,
// so LocalFree has to be called on it when you're finished with it,
// or there will be a memory leak.
//
// Similarly, if a lpLineAddressStatus structure is passed in, it *must*
// have been allocated with LocalAlloc and it could potentially be
// LocalFree()d.
//
// If lpLineAddressStatus == NULL, then a new structure is allocated. It
// is normal to pass in NULL for this parameter unless you want to use a
// lpLineAddressStatus that has been returned by previous
// I_lineGetAddressStatus call.
//
//

LPLINEADDRESSSTATUS I_lineGetAddressStatus(
LPLINEADDRESSSTATUS lpLineAddressStatus,
HLINE hLine, DWORD dwAddressID)
{
size_t sizeofLineAddressStatus = sizeof(LINEADDRESSSTATUS) + 1024;
long lReturn;

// Continue this loop until the structure is big enough.
while(TRUE)
{
// Make sure the buffer exists, is valid and big enough.
lpLineAddressStatus =
(LPLINEADDRESSSTATUS) CheckAndReAllocBuffer(
(LPVOID) lpLineAddressStatus,
sizeofLineAddressStatus,
"lineGetAddressStatus");

if (lpLineAddressStatus == NULL)
return NULL;

// Make the call to fill the structure.
do
{
lReturn =
lineGetAddressStatus(hLine, dwAddressID, lpLineAddressStatus);

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineGetAddressStatus unhandled error: ");
LocalFree(lpLineAddressStatus);
return NULL;
}
}
while (lReturn != SUCCESS);

// If the buffer was big enough, then succeed.
if ((lpLineAddressStatus -> dwNeededSize) <=
(lpLineAddressStatus -> dwTotalSize))
{
return lpLineAddressStatus;
}

// Buffer wasn't big enough. Make it bigger and try again.
sizeofLineAddressStatus = lpLineAddressStatus -> dwNeededSize;
}
}


//
// FUNCTION: I_lineGetCallStatus(LPLINECALLSTATUS, HCALL)
//
// PURPOSE: Retrieve a LINECALLSTATUS structure for the specified line.
//
// PARAMETERS:
// lpLineCallStatus - Pointer to a LINECALLSTATUS structure to use.
// hCall - Handle of call to get the CallStatus of.
//
// RETURN VALUE:
// Returns a pointer to a LINECALLSTATUS structure if successful.
// Returns NULL if unsuccessful.
//
// COMMENTS:
//
// This function is a wrapper around lineGetCallStatus to make it easy
// to handle the variable sized structure and any errors received.
//
// The returned structure has been allocated with LocalAlloc,
// so LocalFree has to be called on it when you're finished with it,
// or there will be a memory leak.
//
// Similarly, if a lpLineCallStatus structure is passed in, it *must*
// have been allocated with LocalAlloc and it could potentially be
// LocalFree()d.
//
// If lpLineCallStatus == NULL, then a new structure is allocated. It
// is normal to pass in NULL for this parameter unless you want to use a
// lpLineCallStatus that has been returned by previous I_lineGetCallStatus
// call.
//
//

LPLINECALLSTATUS I_lineGetCallStatus(
LPLINECALLSTATUS lpLineCallStatus,
HCALL hCall)
{
size_t sizeofLineCallStatus = sizeof(LINECALLSTATUS) + 1024;
long lReturn;

// Continue this loop until the structure is big enough.
while(TRUE)
{
// Make sure the buffer exists, is valid and big enough.
lpLineCallStatus =
(LPLINECALLSTATUS) CheckAndReAllocBuffer(
(LPVOID) lpLineCallStatus,
sizeofLineCallStatus,
"lineGetCallStatus");

if (lpLineCallStatus == NULL)
return NULL;

// Make the call to fill the structure.
do
{
lReturn =
lineGetCallStatus(hCall, lpLineCallStatus);

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineGetCallStatus unhandled error: ");
LocalFree(lpLineCallStatus);
return NULL;
}
}
while (lReturn != SUCCESS);

// If the buffer was big enough, then succeed.
if ((lpLineCallStatus -> dwNeededSize) <=
(lpLineCallStatus -> dwTotalSize))
{
return lpLineCallStatus;
}

// Buffer wasn't big enough. Make it bigger and try again.
sizeofLineCallStatus = lpLineCallStatus -> dwNeededSize;
}
}


//
// FUNCTION: I_lineTranslateAddress
// (LPLINETRANSLATEOUTPUT, DWORD, DWORD, LPCSTR)
//
// PURPOSE: Retrieve a LINECALLSTATUS structure for the specified line.
//
// PARAMETERS:
// lpLineTranslateOutput - Pointer to a LINETRANSLATEOUTPUT structure.
// dwDeviceID - Device that we're translating for.
// dwAPIVersion - API Version to use.
// lpszDialAddress - pointer to the DialAddress string to translate.
//
// RETURN VALUE:
// Returns a pointer to a LINETRANSLATEOUTPUT structure if successful.
// Returns NULL if unsuccessful.
//
// COMMENTS:
//
// This function is a wrapper around lineGetTranslateOutput to make it
// easy to handle the variable sized structure and any errors received.
//
// The returned structure has been allocated with LocalAlloc,
// so LocalFree has to be called on it when you're finished with it,
// or there will be a memory leak.
//
// Similarly, if a lpLineTranslateOutput structure is passed in, it
// *must* have been allocated with LocalAlloc and it could potentially be
// LocalFree()d.
//
// If lpLineTranslateOutput == NULL, then a new structure is allocated.
// It is normal to pass in NULL for this parameter unless you want to use
// a lpLineTranslateOutput that has been returned by previous
// I_lineTranslateOutput call.
//
//

LPLINETRANSLATEOUTPUT I_lineTranslateAddress(
LPLINETRANSLATEOUTPUT lpLineTranslateOutput,
DWORD dwDeviceID, DWORD dwAPIVersion,
LPCSTR lpszDialAddress)
{
size_t sizeofLineTranslateOutput = sizeof(LINETRANSLATEOUTPUT) + 1024;
long lReturn;

// Continue this loop until the structure is big enough.
while(TRUE)
{
// Make sure the buffer exists, is valid and big enough.
lpLineTranslateOutput =
(LPLINETRANSLATEOUTPUT) CheckAndReAllocBuffer(
(LPVOID) lpLineTranslateOutput,
sizeofLineTranslateOutput,
"lineTranslateOutput");

if (lpLineTranslateOutput == NULL)
return NULL;

// Make the call to fill the structure.
do
{
// Note that CALLWAITING is disabled
// (assuming the service provider can disable it)
lReturn =
lineTranslateAddress(g_hLineApp, dwDeviceID, dwAPIVersion,
lpszDialAddress, 0,
LINETRANSLATEOPTION_CANCELCALLWAITING,
lpLineTranslateOutput);

// If the address isn't translatable, notify the user.
if (lReturn == LINEERR_INVALADDRESS)
MessageBox(g_hDlgParentWindow,
"Unable to translate phone number","Warning",MB_OK);

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineTranslateOutput unhandled error: ");
LocalFree(lpLineTranslateOutput);
return NULL;
}
}
while (lReturn != SUCCESS);

// If the buffer was big enough, then succeed.
if ((lpLineTranslateOutput -> dwNeededSize) <=
(lpLineTranslateOutput -> dwTotalSize))
{
return lpLineTranslateOutput;
}

// Buffer wasn't big enough. Make it bigger and try again.
sizeofLineTranslateOutput = lpLineTranslateOutput -> dwNeededSize;
}
}


//
// FUNCTION: I_lineGetAddressCaps(LPLINEADDRESSCAPS, ..)
//
// PURPOSE: Retrieve a LINEADDRESSCAPS structure for the specified line.
//
// PARAMETERS:
// lpLineAddressCaps - Pointer to a LINEADDRESSCAPS, or NULL.
// dwDeviceID - Device to get the address caps for.
// dwAddressID - This sample always assumes the first address.
// dwAPIVersion - API version negotiated for the device.
// dwExtVersion - Always 0 for this sample.
//
// RETURN VALUE:
// Returns a pointer to a LINEADDRESSCAPS structure if successful.
// Returns NULL if unsuccessful.
//
// COMMENTS:
//
// This function is a wrapper around lineGetAddressCaps to make it easy
// to handle the variable sized structure and any errors received.
//
// The returned structure has been allocated with LocalAlloc,
// so LocalFree has to be called on it when you're finished with it,
// or there will be a memory leak.
//
// Similarly, if a lpLineAddressCaps structure is passed in, it *must*
// have been allocated with LocalAlloc and it could potentially be
// LocalFree()d. It also *must* have the dwTotalSize field set.
//
// If lpLineAddressCaps == NULL, then a new structure is allocated. It
// is normal to pass in NULL for this parameter unless you want to use a
// lpLineCallStatus that has been returned by previous I_lineGetAddressCaps
// call.
//
//

LPLINEADDRESSCAPS I_lineGetAddressCaps (
LPLINEADDRESSCAPS lpLineAddressCaps,
DWORD dwDeviceID, DWORD dwAddressID,
DWORD dwAPIVersion, DWORD dwExtVersion)
{
size_t sizeofLineAddressCaps = sizeof(LINEADDRESSCAPS) + 1024;
long lReturn;

// Continue this loop until the structure is big enough.
while(TRUE)
{
// Make sure the buffer exists, is valid and big enough.
lpLineAddressCaps =
(LPLINEADDRESSCAPS) CheckAndReAllocBuffer(
(LPVOID) lpLineAddressCaps,
sizeofLineAddressCaps,
"lineGetAddressCaps");

if (lpLineAddressCaps == NULL)
return NULL;

// Make the call to fill the structure.
do
{
lReturn =
lineGetAddressCaps(g_hLineApp,
dwDeviceID, dwAddressID, dwAPIVersion, dwExtVersion,
lpLineAddressCaps);

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineGetAddressCaps unhandled error: ");
LocalFree(lpLineAddressCaps);
return NULL;
}
}
while (lReturn != SUCCESS);

// If the buffer was big enough, then succeed.
if ((lpLineAddressCaps -> dwNeededSize) <=
(lpLineAddressCaps -> dwTotalSize))
{
return lpLineAddressCaps;
}

// Buffer wasn't big enough. Make it bigger and try again.
sizeofLineAddressCaps = lpLineAddressCaps -> dwNeededSize;

} 
}



//**************************************************
// LINEERR Error Handlers
//**************************************************


//
// FUNCTION: HandleLineErr(long)
//
// PURPOSE: Handle several standard LINEERR errors
//
// PARAMETERS:
// lLineErr - Error code to be handled.
//
// RETURN VALUE:
// Return TRUE if lLineErr wasn't an error, or if the
// error was successfully handled and cleared up.
// Return FALSE if lLineErr was an unhandled error.
//
// COMMENTS:
//
// This is the main error handler for all TAPI line APIs.
// It handles (by correcting or just notifying the user)
// most of the errors that can occur while using TAPI line APIs.
//
// Note that many errors still return FALSE (unhandled) even
// if a dialog is displayed. Often, the dialog is just notifying
// the user why the action was canceled.
//
//
//

BOOL HandleLineErr(long lLineErr)
{
// lLineErr is really an async request ID, not an error.
if (lLineErr > SUCCESS)
return FALSE;

// All we do is dispatch the correct error handler.
switch(lLineErr)
{
case SUCCESS:
return TRUE;

case LINEERR_INVALCARD:
case LINEERR_INVALLOCATION:
case LINEERR_INIFILECORRUPT:
return HandleIniFileCorrupt();

case LINEERR_NODRIVER:
return HandleNoDriver();

case LINEERR_REINIT:
return HandleReInit();

case LINEERR_NOMULTIPLEINSTANCE:
return HandleNoMultipleInstance();

case LINEERR_NOMEM:
return HandleNoMem();

case LINEERR_OPERATIONFAILED:
return HandleOperationFailed();

case LINEERR_RESOURCEUNAVAIL:
return HandleResourceUnavail();

// Unhandled errors fail.
default:
return FALSE;
}
}



//
// FUNCTION: HandleIniFileCorrupt
//
// PURPOSE: Handle INIFILECORRUPT error.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE - error was corrected.
// FALSE - error was not corrected.
//
// COMMENTS:
//
// This error shouldn't happen under Windows 95 anymore. The TAPI.DLL
// takes care of correcting this problem. If it does happen, just
// notify the user.
//

BOOL HandleIniFileCorrupt()
{
if (IDCANCEL == MessageBox(g_hDlgParentWindow,
"Configuration information relating to Telephony "
"services appears to be corrupt.\n"
"This could be the first time you have used the Telephony services.\n"
"Would you like to configure the Telephony services?",
"Warning",MB_OKCANCEL))
{
return FALSE;
}

lineTranslateDialog(g_hLineApp, 0, SAMPLE_TAPI_VERSION,
g_hDlgParentWindow, NULL);

return TRUE;
}


//
// FUNCTION: HandleNoDriver
//
// PURPOSE: Handle NODRIVER error.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE - error was corrected.
// FALSE - error was not corrected.
//
// COMMENTS:
//
//

BOOL HandleNoDriver()
{
int mbRet;
mbRet = MessageBox(g_hDlgParentWindow,
"One of the components of the Telephony device driver is missing.\n"
"Use the Control Panel to set up the driver properly.",
"Warning",MB_OK);

return FALSE;
}


//
// FUNCTION: HandleNoMultipleInstance
//
// PURPOSE: Handle NOMULTIPLEINSTANCE error.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE - error was corrected.
// FALSE - error was not corrected.
//
// COMMENTS:
//
//

BOOL HandleNoMultipleInstance()
{
MessageBox(g_hDlgParentWindow,
"You have two copies of the same Telephony driver installed.\n"
"Use the Control Panel to remove one of the copies.",
"Warning",MB_OK);

return FALSE;
}


//
// FUNCTION: HandleReInit
//
// PURPOSE: Handle REINIT error.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE - error was corrected.
// FALSE - error was not corrected.
//
// COMMENTS:
//
//

BOOL HandleReInit()
{
ShutdownTAPI();
return FALSE;
}


//
// FUNCTION: HandleNoMem
//
// PURPOSE: Handle NOMEM error.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE - error was corrected.
// FALSE - error was not corrected.
//
// COMMENTS:
// This is also called if I run out of memory for LocalAlloc()s
//
//

BOOL HandleNoMem()
{
MessageBox(g_hDlgParentWindow,
"Out of Memory error, canceling action.","Error", MB_OK);
return FALSE;
}


//
// FUNCTION: HandleOperationFailed
//
// PURPOSE: Handle OPERATIONFAILED error.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE - error was corrected.
// FALSE - error was not corrected.
//
// COMMENTS:
//
//

BOOL HandleOperationFailed()
{
MessageBox(g_hDlgParentWindow,
"TAPI Operation Failed for unknown reasons.",
"Error", MB_OK);
return FALSE;
}


//
// FUNCTION: HandleResourceUnavail
//
// PURPOSE: Handle RESOURCEUNAVAIL error.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE - error was corrected.
// FALSE - error was not corrected.
//
// COMMENTS:
//
//

BOOL HandleResourceUnavail()
{
int mbRet;

mbRet = MessageBox(g_hDlgParentWindow,
"A Telephony resource is temporarily unavaiable. "
"This could mean a short wait is necessary or "
"that a non-TAPI application is using the line.",
"Warning",MB_RETRYCANCEL);

if (mbRet == IDRETRY)
return TRUE;

return FALSE;
}


//
// FUNCTION: HandleNoDevicesInstalled
//
// PURPOSE: Handle cases when we know NODEVICE error
// is returned because there are no devices installed.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE - error was corrected.
// FALSE - error was not corrected.
//
// COMMENTS:
//
// This function is not part of standard error handling
// but is only used when we know that the NODEVICE error
// means that no devices are installed.
//
//

BOOL HandleNoDevicesInstalled()
{
int mbRet;
mbRet = MessageBox(g_hDlgParentWindow,
"There are no devices configured for Telephony use.\n"
"Would you like to run the Modem Control Panel to add a modem driver?",
"Warning",MB_YESNO);

if (mbRet == IDYES)
{
if (LaunchModemControlPanelAdd())
return TRUE;
}

return FALSE;
}


//
// FUNCTION: LaunchModemControlPanelAdd
//
// PURPOSE: Launch Add Modem Control Panel applet.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE - Control Panel launched successfully.
// FALSE - It didn't.
//
// COMMENTS:
//
//

BOOL LaunchModemControlPanelAdd()
{
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartupInfo;

siStartupInfo.cb = sizeof(STARTUPINFO);
siStartupInfo.lpReserved = NULL;
siStartupInfo.lpDesktop = NULL;
siStartupInfo.lpTitle = NULL;
siStartupInfo.dwFlags = STARTF_USESHOWWINDOW;
siStartupInfo.wShowWindow = SW_SHOWNORMAL;
siStartupInfo.cbReserved2 = 0;
siStartupInfo.lpReserved2 = NULL;

// The string to launch the modem control panel is *VERY* likely
// to change on NT. If nothing else, this is 'contrl32' on NT
// instead of 'control'.
if (CreateProcess(
NULL,
"CONTROL.EXE MODEM.CPL,,ADD",
NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS,
NULL, NULL,
&siStartupInfo,
&piProcInfo))
{
CloseHandle(piProcInfo.hThread);


// Control panel 'Add New Modem' has been launched. Now we should
// wait for it to go away before continueing.

// If we WaitForSingleObject for the control panel to exit, then we
// get into a deadlock situation if we need to respond to any messages
// from the control panel.

// If we use a PeekMessage loop to wait, we run into
// message re-entrancy problems. (The user can get back to our UI
// and click 'dial' again).

// Instead, we take the easy way out and return FALSE to abort
// the current operation.

CloseHandle(piProcInfo.hProcess);
}
else
{
OutputDebugLastError(GetLastError(),
"Unable to LaunchModemControlPanelAdd: ");
MessageBox(g_hDlgParentWindow,
"Unable to launch the Modem Control Panel",
"Warning", MB_OK);
}

return FALSE;
}


//
// FUNCTION: WarningBox(LPCSTR)
//
// PURPOSE: Prints a warning box when conditions remove a line in use.
//
// PARAMETERS:
// lpszMessage - String that specifies why the line was removed form use.
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// If there is a call in progress on the line removed, then display a message
// specifying why the line was removed and that the call is being canceled.
//
//

void WarningBox(LPCSTR lpszMessage)
{
char szBuff[1024];

strcpy(szBuff, lpszMessage);

// If there is a call open, tell user we're going to close it.
if (g_hCall)
strcat(szBuff, "\nClosing existing call.");

MessageBox(g_hDlgParentWindow, szBuff, "Warning", MB_OK);

strcat(szBuff, "\r\n");
OutputDebugString(szBuff);
}


//**************************************************
//
// All the functions from this point on are used solely by the "Dial" dialog.
// This dialog is used to get both the 'phone number' address,
// the line device to be used as well as allow the user to configure
// dialing properties and the line device.
//
//**************************************************

//
// FUNCTION: DWORD I_lineNegotiateLegacyAPIVersion(DWORD)
//
// PURPOSE: Negotiate an API Version to use for a specific device.
//
// PARAMETERS:
// dwDeviceID - device to negotiate an API Version for.
//
// RETURN VALUE:
// Returns the API Version to use for this line if successful.
// Returns 0 if negotiations fall through.
//
// COMMENTS:
//
// This wrapper is slightly different from the I_lineNegotiateAPIVersion.
// This wrapper allows TapiComm to negotiate an API version between
// 1.3 and SAMPLE_TAPI_VERSION. Normally, this sample is specific to
// API Version SAMPLE_TAPI_VERSION. However, there are a few times when
// TapiComm needs to get information from a service provider, but also knows
// that a lower API Version would be ok. This allows TapiComm to recognize
// legacy service providers even though it can't use them. 1.3 is the
// lowest API Version a legacy service provider should support.
//
//

DWORD I_lineNegotiateLegacyAPIVersion(DWORD dwDeviceID)
{
LINEEXTENSIONID LineExtensionID;
long lReturn;
DWORD dwLocalAPIVersion;

do
{
lReturn = lineNegotiateAPIVersion(g_hLineApp, dwDeviceID,
0x00010003, SAMPLE_TAPI_VERSION,
&dwLocalAPIVersion, &LineExtensionID);

if (lReturn == LINEERR_INCOMPATIBLEAPIVERSION)
{
OutputDebugString("INCOMPATIBLEAPIVERSION in Dial Dialog.\n");
return 0;
}

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineNegotiateAPIVersion in Dial Dialog unhandled error: ");
return 0;
}
}
while(lReturn != SUCCESS);

return dwLocalAPIVersion;
}


//
// FUNCTION: long VerifyUsableLine(DWORD)
//
// PURPOSE: Verifies that a specific line device is useable by TapiComm.
//
// PARAMETERS:
// dwDeviceID - The ID of the line device to be verified
//
// RETURN VALUE:
// Returns SUCCESS if dwDeviceID is a usable line device.
// Returns a LINENOTUSEABLE_ constant otherwise.
//
// COMMENTS:
//
// VerifyUsableLine takes the give device ID and verifies step by step
// that the device supports all the features that TapiComm requires.
//
//

long VerifyUsableLine(DWORD dwDeviceID)
{
LPLINEDEVCAPS lpLineDevCaps = NULL;
LPLINEADDRESSSTATUS lpLineAddressStatus = NULL;
LPVARSTRING lpVarString = NULL;
DWORD dwAPIVersion;
long lReturn;
long lUsableLine = SUCCESS;
HLINE hLine = 0;

OutputDebugPrintf("Testing Line ID '0x%lx'",dwDeviceID);

// The line device must support an API Version that TapiComm does.
dwAPIVersion = I_lineNegotiateAPIVersion(dwDeviceID);
if (dwAPIVersion == 0)
return LINENOTUSEABLE_ERROR;

lpLineDevCaps = I_lineGetDevCaps(lpLineDevCaps,
dwDeviceID, dwAPIVersion);

if (lpLineDevCaps == NULL)
return LINENOTUSEABLE_ERROR;

// Must support LINEBEARERMODE_VOICE
if (!(lpLineDevCaps->dwBearerModes & LINEBEARERMODE_VOICE ))
{
lUsableLine = LINENOTUSEABLE_NOVOICE;
OutputDebugString("LINEBEARERMODE_VOICE not supported\n");
goto DeleteBuffers;
}

// Must support LINEMEDIAMODE_DATAMODEM
if (!(lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_DATAMODEM))
{
lUsableLine = LINENOTUSEABLE_NODATAMODEM;
OutputDebugString("LINEMEDIAMODE_DATAMODEM not supported\n");
goto DeleteBuffers;
}

// Must be able to make calls
if (!(lpLineDevCaps->dwLineFeatures & LINEFEATURE_MAKECALL))
{
lUsableLine = LINENOTUSEABLE_NOMAKECALL;
OutputDebugString("LINEFEATURE_MAKECALL not supported\n");
goto DeleteBuffers;
}

// It is necessary to open the line so we can check if
// there are any call appearances available. Other TAPI
// applications could be using all call appearances.
// Opening the line also checks for other possible problems.
do
{
lReturn = lineOpen(g_hLineApp, dwDeviceID, &hLine,
dwAPIVersion, 0, 0,
LINECALLPRIVILEGE_NONE, LINEMEDIAMODE_DATAMODEM,
0);

if(lReturn == LINEERR_ALLOCATED)
{
OutputDebugString(
"Line is already in use by a non-TAPI app or"
" another Service Provider.\n");
lUsableLine = LINENOTUSEABLE_ALLOCATED;
goto DeleteBuffers;
}

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn, "lineOpen unhandled error: ");
lUsableLine = LINENOTUSEABLE_ERROR;
goto DeleteBuffers;
}
}
while(lReturn != SUCCESS);

// Get LineAddressStatus to make sure the line isn't already in use.
lpLineAddressStatus =
I_lineGetAddressStatus(lpLineAddressStatus, hLine, 0);

if (lpLineAddressStatus == NULL)
{
lUsableLine = LINENOTUSEABLE_ERROR;
goto DeleteBuffers;
}

// Are there any available call appearances (ie: is it in use)?
if ( !((lpLineAddressStatus -> dwAddressFeatures) &
LINEADDRFEATURE_MAKECALL) )
{
OutputDebugString("LINEADDRFEATURE_MAKECALL not available\n");
lUsableLine = LINENOTUSEABLE_INUSE;
goto DeleteBuffers;
}

// Make sure the "comm/datamodem" device class is supported
// Note that we don't want any of the 'extra' information
// normally returned in the VARSTRING structure. All we care
// about is if lineGetID succeeds.
do
{
lpVarString = CheckAndReAllocBuffer((LPVOID) lpVarString,
sizeof(VARSTRING),"VerifyUsableLine:lineGetID: ");

if (lpVarString == NULL)
{
lUsableLine = LINENOTUSEABLE_ERROR;
goto DeleteBuffers;
}

lReturn = lineGetID(hLine, 0, 0, LINECALLSELECT_LINE,
lpVarString, "comm/datamodem");

if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugLineError(lReturn,
"lineGetID unhandled error: ");
lUsableLine = LINENOTUSEABLE_NOCOMMDATAMODEM;
goto DeleteBuffers;
}
}
while(lReturn != SUCCESS);

OutputDebugString("Line is suitable and available for use.\n");

DeleteBuffers:

if (hLine)
lineClose(hLine);
if (lpLineAddressStatus)
LocalFree(lpLineAddressStatus);
if (lpLineDevCaps)
LocalFree(lpLineDevCaps);
if (lpVarString)
LocalFree(lpVarString);

return lUsableLine;
}


//
// FUNCTION: void FillTAPILine(HWND)
//
// PURPOSE: Fills the 'TAPI Line' control with the available line devices.
//
// PARAMETERS:
// hwndDlg - handle to the current "Dial" dialog
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// This function enumerates through all the TAPI line devices and
// queries each for the device name. The device name is then put into
// the 'TAPI Line' control. These device names are kept in order rather
// than sorted. This allows "Dial" to know which device ID the user
// selected just by the knowing the index of the selected string.
//
// There are default values if there isn't a device name, if there is
// an error on the device, or if the device name is an empty string.
// The device name is also checked to make sure it is null terminated.
//
// Note that a Legacy API Version is negotiated. Since the fields in
// the LINEDEVCAPS structure that we are interested in haven't moved, we
// can negotiate a lower API Version than this sample is designed for
// and still be able to access the necessary structure members.
//
// The first line that is usable by TapiComm is selected as the 'default'
// line. Also note that if there was a previously selected line, this
// remains the default line. This would likely only occur if this
// function is called after the dialog has initialized once; for example,
// if a new line is added.
//
//

void FillTAPILine(HWND hwndDlg)
{
DWORD dwDeviceID;
DWORD dwAPIVersion;
LPLINEDEVCAPS lpLineDevCaps = NULL;
char szLineUnavail[] = "Line Unavailable";
char szLineUnnamed[] = "Line Unnamed";
char szLineNameEmpty[] = "Line Name is Empty";
LPSTR lpszLineName;
long lReturn;
DWORD dwDefaultDevice = MAXDWORD;

// Make sure the control is empty. If it isn't,
// hold onto the currently selected ID and then reset it.
if (SendDlgItemMessage(hwndDlg, IDC_TAPILINE, CB_GETCOUNT, 0, 0))
{
dwDefaultDevice = SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
CB_GETCURSEL, 0, 0);
SendDlgItemMessage(hwndDlg, IDC_TAPILINE, CB_RESETCONTENT, 0, 0);
}

for (dwDeviceID = 0; dwDeviceID < g_dwNumDevs; dwDeviceID ++)
{
dwAPIVersion = I_lineNegotiateLegacyAPIVersion(dwDeviceID);
if (dwAPIVersion)
{
lpLineDevCaps = I_lineGetDevCaps(lpLineDevCaps,
dwDeviceID, dwAPIVersion);
if (lpLineDevCaps)
{
if ((lpLineDevCaps -> dwLineNameSize) &&
(lpLineDevCaps -> dwLineNameOffset) &&
(lpLineDevCaps -> dwStringFormat == STRINGFORMAT_ASCII))
{
// This is the name of the device.
lpszLineName = ((char *) lpLineDevCaps) +
lpLineDevCaps -> dwLineNameOffset;

if (lpszLineName[0] != '\0')
{
// Reverse indented to make this fit

// Make sure the device name is null terminated.
if (lpszLineName[lpLineDevCaps->dwLineNameSize -1] != '\0')
{
// If the device name is not null terminated, null
// terminate it. Yes, this looses the end character.
// Its a bug in the service provider.
lpszLineName[lpLineDevCaps->dwLineNameSize-1] = '\0';
OutputDebugPrintf(
"Device name for device 0x%lx is not null terminated.\n",
dwDeviceID);
}
}
else // Line name started with a NULL.
lpszLineName = szLineNameEmpty;
}
else // DevCaps doesn't have a valid line name. Unnamed.
lpszLineName = szLineUnnamed;
}
else // Couldn't GetDevCaps. Line is unavail.
lpszLineName = szLineUnavail;
}
else // Couldn't NegotiateAPIVersion. Line is unavail.
lpszLineName = szLineUnavail;

// Put the device name into the control
lReturn = SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
CB_ADDSTRING, 0, (LPARAM) (LPCTSTR) lpszLineName);

// If this line is usable and we don't have a default initial
// line yet, make this the initial line.
if ((lpszLineName != szLineUnavail) &&
(dwDefaultDevice == MAXDWORD) &&
(VerifyUsableLine(dwDeviceID) == SUCCESS))
{
dwDefaultDevice = dwDeviceID;
}
}

if (lpLineDevCaps)
LocalFree(lpLineDevCaps);

if (dwDefaultDevice == MAXDWORD)
dwDefaultDevice = 0;

// Set the initial default line
SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
CB_SETCURSEL, dwDefaultDevice, 0);
}


//
// FUNCTION: BOOL VerifyAndWarnUsableLine(HWND)
//
// PURPOSE: Verifies the line device selected by the user.
//
// PARAMETERS:
// hwndDlg - The handle to the current "Dial" dialog.
//
// RETURN VALUE:
// Returns TRUE if the currently selected line device is useable
// by TapiComm. Returns FALSE if it isn't.
//
// COMMENTS:
//
// This function is very specific to the "Dial" dialog. It gets
// the device selected by the user from the 'TAPI Line' control and
// VerifyUsableLine to make sure this line device is usable. If the
// line isn't useable, it notifies the user and disables the 'Dial'
// button so that the user can't initiate a call with this line.
//
// This function is also responsible for filling in the line specific
// icon found on the "Dial" dialog.
//
//

BOOL VerifyAndWarnUsableLine(HWND hwndDlg)
{
DWORD dwDeviceID;
long lReturn;
HICON hIcon = 0;
HWND hControlWnd;

// Get the selected line device.
dwDeviceID = SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
CB_GETCURSEL, 0, 0);

// Get the "comm" device icon associated with this line device.
lReturn = lineGetIcon(dwDeviceID, "comm", &hIcon);

if (lReturn == SUCCESS)
SendDlgItemMessage(hwndDlg, IDC_LINEICON, STM_SETICON,
(WPARAM) hIcon, 0);
else
// Any failure to get an icon makes us use the default icon.
SendDlgItemMessage(hwndDlg, IDC_LINEICON, WM_SETTEXT,
0, (LPARAM) (LPCTSTR) "TapiComm");

/* // It turns out that TAPI will always return an icon, even if
// the device class isn't supported by the TSP or even if the TSP
// doesn't return any icons at all. This code is unnecessary.
// The only reason lineGetIcon would fail is due to resource problems.

else
{
// If the line doesn't have a "comm" device icon, use its default one.
lReturn = lineGetIcon(dwDeviceID, NULL, &hIcon);
if (lReturn == SUCCESS)
{
OutputDebugString("Line doesn't support a \"comm\" icon.\n");
SendDlgItemMessage(hwndDlg, IDC_LINEICON, STM_SETICON,
(WPARAM) hIcon, 0);
}
else
{
// If lineGetIcon fails, just use TapiComms icon.
OutputDebugLineError(lReturn, "lineGetIcon: ");
SendDlgItemMessage(hwndDlg, IDC_LINEICON, WM_SETTEXT,
0, (LPARAM) (LPCTSTR) "TapiComm");
}
}
*/

// Verify if the device is usable by TapiComm.
lReturn = VerifyUsableLine(dwDeviceID);

// Enable or disable the 'Dial' button, depending on if the line is ok.
// Make sure there is a number to dial before enabling the button.
hControlWnd = GetDlgItem(hwndDlg, IDC_DIAL);
if (SendDlgItemMessage(hwndDlg, IDC_CANONICALNUMBER,
WM_GETTEXTLENGTH, 0, 0) == 0)
{
EnableWindow(hControlWnd, FALSE);
}
else
EnableWindow(hControlWnd, (lReturn == SUCCESS));

// Any errors on this line prevent us from configuring it
// or using dialing properties.
if (lReturn == LINENOTUSEABLE_ERROR)
{
EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGURELINE), FALSE);
EnableWindow(GetDlgItem(hwndDlg, IDC_DIALINGPROPERTIES), FALSE);
}
else
{
EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGURELINE), TRUE);
if (SendDlgItemMessage(hwndDlg, IDC_USEDIALINGRULES, BM_GETCHECK, 0, 0))
EnableWindow(GetDlgItem(hwndDlg, IDC_DIALINGPROPERTIES), TRUE);
}

switch(lReturn)
{
case SUCCESS:
g_dwDeviceID = dwDeviceID;
return TRUE;

case LINENOTUSEABLE_ERROR:
MessageBox(hwndDlg,
"The selected line is incompatible with the TapiComm sample",
"Warning",MB_OK);
break;
case LINENOTUSEABLE_NOVOICE:
MessageBox(hwndDlg,
"The selected line doesn't support VOICE capabilities",
"Warning",MB_OK);
break;
case LINENOTUSEABLE_NODATAMODEM:
MessageBox(hwndDlg,
"The selected line doesn't support DATAMODEM capabilities",
"Warning",MB_OK);
break;
case LINENOTUSEABLE_NOMAKECALL:
MessageBox(hwndDlg,
"The selected line doesn't support MAKECALL capabilities",
"Warning",MB_OK);
break;
case LINENOTUSEABLE_ALLOCATED:
MessageBox(hwndDlg,
"The selected line is already in use by a non-TAPI application",
"Warning",MB_OK);
break;
case LINENOTUSEABLE_INUSE:
MessageBox(hwndDlg,
"The selected line is already in use by a TAPI application",
"Warning",MB_OK);
break;

case LINENOTUSEABLE_NOCOMMDATAMODEM:
MessageBox(hwndDlg,
"The selected line doesn't support the COMM/DATAMODEM device class",
"Warning",MB_OK);
break;
}

// g_dwDeviceID == MAXDWORD mean the selected device isn't usable.
g_dwDeviceID = MAXDWORD;
return FALSE;
}


//
// FUNCTION: void FillCountryCodeList(HWND, DWORD)
//
// PURPOSE: Fill the 'Country Code' control
//
// PARAMETERS:
// hwndDlg - handle to the current "Dial" dialog
// dwDefaultCountryID - ID of the 'default' country to be selected
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// This function fills the 'Country Code' control with country names.
// The country code is appended to the end of the name and the names
// are added to the control sorted. Because the country code is
// embedded in the string along with the country name, there is no need
// for any of the country information structures to be kept around. The
// country code can be extracted from the selected string at any time.
//
//

void FillCountryCodeList(HWND hwndDlg, DWORD dwDefaultCountryID)
{
LPLINECOUNTRYLIST lpLineCountryList = NULL;
DWORD dwSizeofCountryList = sizeof(LINECOUNTRYLIST);
long lReturn;
DWORD dwCountry;
LPLINECOUNTRYENTRY lpLineCountryEntries;
char szRenamedCountry[256];

// Get the country information stored in TAPI
do
{
lpLineCountryList = (LPLINECOUNTRYLIST) CheckAndReAllocBuffer(
(LPVOID) lpLineCountryList, dwSizeofCountryList,
"FillCountryCodeList");

if (lpLineCountryList == NULL)
return;

lReturn = lineGetCountry (0, SAMPLE_TAPI_VERSION, lpLineCountryList);

if (HandleLineErr(lReturn))
;
else
{
OutputDebugLineError(lReturn, "lineGetCountry unhandled error: ");
LocalFree(lpLineCountryList);
return;
}

if ((lpLineCountryList -> dwNeededSize) >
(lpLineCountryList -> dwTotalSize))
{
dwSizeofCountryList = lpLineCountryList ->dwNeededSize;

lReturn = -1; // Lets loop again. 
}
}
while (lReturn != SUCCESS);

// Find the first country entry
lpLineCountryEntries = (LPLINECOUNTRYENTRY)
(((LPBYTE) lpLineCountryList)
+ lpLineCountryList -> dwCountryListOffset);

// Now enumerate through all the countries
for (dwCountry = 0;
dwCountry < lpLineCountryList -> dwNumCountries;
dwCountry++)
{
// append the country code to the country name
wsprintf(szRenamedCountry,"%s (%lu)",
(((LPSTR) lpLineCountryList) +
lpLineCountryEntries[dwCountry].dwCountryNameOffset),
lpLineCountryEntries[dwCountry].dwCountryCode);

// Now put this country name / code string into the combobox
lReturn = SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE, CB_ADDSTRING,
0, (LPARAM) (LPCTSTR) szRenamedCountry);

// If this country is the default country, select it.
if (lpLineCountryEntries[dwCountry].dwCountryID
== dwDefaultCountryID)
{
SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE, CB_SETCURSEL, lReturn, 0);
}
}

LocalFree(lpLineCountryList);
return;
}


//
// FUNCTION: void FillLocationInfo(HWND, LPSTR, LPDWORD, LPSTR)
//
// PURPOSE: Fill (or refill) the 'Your Location' control
//
// PARAMETERS:
// hwndDlg - handle to the current "Dial" dialog
// lpszCurrentLocation - Name of current location, or NULL
// lpdwCountryID - location to store the current country ID or NULL
// lpszAreaCode - location to store the current area code or NULL
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// This function is moderately multipurpose.
//
// If lpszCurrentLocation is NULL, then the 'Your Location' control
// is filled with all the locations stored in TAPI and the TAPI 'default'
// location is selected. This is done during initialization and
// also after the 'Dialing Properties' dialog has been displayed.
// This last is done because the user can change the current location
// or add and delete locations while in the 'Dialing Properties' dialog.
//
// If lpszCurrentLocation is a valid string pointer, then it is assumed
// that the 'Your Location' control is already filled and that the user
// is selecting a specific location. In this case, all of the existing
// TAPI locations are enumerated until the specified location is found.
// At this point, the specified location is set to the current location.
//
// In either case, if lpdwCountryID is not NULL, it is filled with the
// country ID for the current location. If lpszAreaCode is not NULL, it
// is filled with the area code defined for the current location. These
// values can be used later to initialize other "Dial" controls.
//
// This function also fills the 'Calling Card' control based on
// the information stored in the current location.
//
//

void FillLocationInfo(HWND hwndDlg, LPSTR lpszCurrentLocation,
LPDWORD lpdwCountryID, LPSTR lpszAreaCode)
{
LPLINETRANSLATECAPS lpTranslateCaps = NULL;
DWORD dwSizeofTranslateCaps = sizeof(LINETRANSLATECAPS);
long lReturn;
DWORD dwCounter;
LPLINELOCATIONENTRY lpLocationEntry;
LPLINECARDENTRY lpLineCardEntry = NULL;
DWORD dwPreferredCardID = MAXDWORD;

// First, get the TRANSLATECAPS
do
{
lpTranslateCaps = (LPLINETRANSLATECAPS) CheckAndReAllocBuffer(
(LPVOID) lpTranslateCaps, dwSizeofTranslateCaps,
"FillLocationInfo");

if (lpTranslateCaps == NULL)
return;

lReturn = lineGetTranslateCaps(g_hLineApp, SAMPLE_TAPI_VERSION,
lpTranslateCaps);

if (HandleLineErr(lReturn))
;
else
{
OutputDebugLineError(lReturn,
"lineGetTranslateCaps unhandled error: ");
LocalFree(lpTranslateCaps);
return;
}

if ((lpTranslateCaps -> dwNeededSize) >
(lpTranslateCaps -> dwTotalSize))
{
dwSizeofTranslateCaps = lpTranslateCaps ->dwNeededSize;
lReturn = -1; // Lets loop again.
}
}
while(lReturn != SUCCESS);

// Find the location information in the TRANSLATECAPS
lpLocationEntry = (LPLINELOCATIONENTRY)
(((LPBYTE) lpTranslateCaps) + lpTranslateCaps->dwLocationListOffset);

// If lpszCurrentLocation, then make that location 'current'
if (lpszCurrentLocation)
{
// loop through all locations, looking for a location match
for(dwCounter = 0;
dwCounter < lpTranslateCaps -> dwNumLocations;
dwCounter++)
{
if (strcmp((((LPSTR) lpTranslateCaps) +
lpLocationEntry[dwCounter].dwLocationNameOffset),
lpszCurrentLocation)
== 0)
{
// Found it! Set the current location.
lineSetCurrentLocation(g_hLineApp,
lpLocationEntry[dwCounter].dwPermanentLocationID);

// Set the return values.
if (lpdwCountryID)
*lpdwCountryID = lpLocationEntry[dwCounter].dwCountryID;

if (lpszAreaCode)
strcpy(lpszAreaCode, (((LPSTR) lpTranslateCaps) +
lpLocationEntry[dwCounter].dwCityCodeOffset));

// Store the preferred card ID for later use.
dwPreferredCardID = lpLocationEntry[dwCounter].dwPreferredCardID;
break;
}
}

// Was a match for lpszCurrentLocation found?
if (dwPreferredCardID == MAXDWORD)
{
OutputDebugString("lpszCurrentLocation not found\n");
SendDlgItemMessage(hwndDlg, IDC_CALLINGCARD, WM_SETTEXT, 0,
(LPARAM) (LPCSTR) "Invalid Location Selected");
LocalFree(lpTranslateCaps);
return;
}
}
else // fill the combobox and use the TAPI 'current' location.
{
// First empty the combobox
SendDlgItemMessage(hwndDlg, IDC_LOCATION, CB_RESETCONTENT, 0, 0);

// enumerate all the locations
for(dwCounter = 0;
dwCounter < lpTranslateCaps -> dwNumLocations;
dwCounter++)
{
// Put each one into the combobox
lReturn = SendDlgItemMessage(hwndDlg, IDC_LOCATION, CB_ADDSTRING,
0, (LPARAM) (((LPBYTE) lpTranslateCaps) +
lpLocationEntry[dwCounter].dwLocationNameOffset));

// Is this location the 'current' location?
if (lpLocationEntry[dwCounter].dwPermanentLocationID ==
lpTranslateCaps->dwCurrentLocationID)
{
// Return the requested information
if (lpdwCountryID)
*lpdwCountryID = lpLocationEntry[dwCounter].dwCountryID;

if (lpszAreaCode)
strcpy(lpszAreaCode, (((LPSTR) lpTranslateCaps) +
lpLocationEntry[dwCounter].dwCityCodeOffset));

// Set this to be the active location.
SendDlgItemMessage(hwndDlg, IDC_LOCATION, CB_SETCURSEL, lReturn, 0);
dwPreferredCardID = lpLocationEntry[dwCounter].dwPreferredCardID;
}
}
}

// Now locate the prefered card and display it.

lpLineCardEntry = (LPLINECARDENTRY)
(((LPBYTE) lpTranslateCaps) + lpTranslateCaps->dwCardListOffset);

for(dwCounter = 0;
dwCounter < lpTranslateCaps -> dwNumCards;
dwCounter++)
{
if (lpLineCardEntry[dwCounter].dwPermanentCardID == dwPreferredCardID)
{
SendDlgItemMessage(hwndDlg, IDC_CALLINGCARD, WM_SETTEXT, 0,
(LPARAM) (((LPBYTE) lpTranslateCaps) +
lpLineCardEntry[dwCounter].dwCardNameOffset));
break;
}
}

LocalFree(lpTranslateCaps);
}



//
// FUNCTION: void UseDialingRules(HWND)
//
// PURPOSE: Enable/disable Dialing Rule controls
//
// PARAMETERS:
// hwndDlg - handle to the current "Dial" dialog
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// The sole purpose of this function is to enable or disable
// the controls that apply to dialing rules if the
// "Use Country Code and Area Code" checkbox is checked or unchecked,
// as appropriate.
//
//

void UseDialingRules(HWND hwndDlg)
{
HWND hControl;
BOOL bEnableWindow;

bEnableWindow = SendDlgItemMessage(hwndDlg,
IDC_USEDIALINGRULES, BM_GETCHECK, 0, 0);

hControl = GetDlgItem(hwndDlg, IDC_STATICCOUNTRYCODE);
EnableWindow(hControl, bEnableWindow);

hControl = GetDlgItem(hwndDlg, IDC_COUNTRYCODE);
EnableWindow(hControl, bEnableWindow);

hControl = GetDlgItem(hwndDlg, IDC_STATICAREACODE);
EnableWindow(hControl, bEnableWindow);

hControl = GetDlgItem(hwndDlg, IDC_AREACODE);
EnableWindow(hControl, bEnableWindow);

hControl = GetDlgItem(hwndDlg, IDC_STATICLOCATION);
EnableWindow(hControl, bEnableWindow);

hControl = GetDlgItem(hwndDlg, IDC_LOCATION);
EnableWindow(hControl, bEnableWindow);

hControl = GetDlgItem(hwndDlg, IDC_STATICCALLINGCARD);
EnableWindow(hControl, bEnableWindow);

hControl = GetDlgItem(hwndDlg, IDC_CALLINGCARD);
EnableWindow(hControl, bEnableWindow);

if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_CONFIGURELINE)))
{
hControl = GetDlgItem(hwndDlg, IDC_DIALINGPROPERTIES);
EnableWindow(hControl, bEnableWindow);
}
}


//
// FUNCTION: void DisplayPhoneNumber(HWND)
//
// PURPOSE: Create, Translate and Display the Phone Number
//
// PARAMETERS:
// hwndDlg - handle to the current "Dial" dialog
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// This function uses the information stored in many other controls
// to build the phone number, translate it, and display it. Also
// makes sure the Dial button is enabled or disabled, based on if the
// number can be dialed or not.
//
// There are actually three phone numbers generated during this
// process: canonical, dialable and displayable. Normally, only the
// displayable number is shown to the user; the other two numbers are
// to be used by the program internally. However, for demonstration
// purposes (and because it is cool for developers to see these numbers),
// all three numbers are displayed.
//

void DisplayPhoneNumber(HWND hwndDlg)
{
char szPreTranslatedNumber[1024] = "";
int nPreTranslatedSize = 0;
char szTempBuffer[512];
int i;
DWORD dwDeviceID;
LPLINETRANSLATEOUTPUT lpLineTranslateOutput = NULL;

// Disable the 'dial' button if there isn't a number to dial
if (0 == SendDlgItemMessage(hwndDlg, IDC_PHONENUMBER,
WM_GETTEXTLENGTH, 0, 0))
{
SendDlgItemMessage(hwndDlg, IDC_CANONICALNUMBER, WM_SETTEXT, 0,
(LPARAM) NULL);
SendDlgItemMessage(hwndDlg, IDC_DIALABLENUMBER, WM_SETTEXT, 0,
(LPARAM) NULL);
SendDlgItemMessage(hwndDlg, IDC_DISPLAYABLENUMBER, WM_SETTEXT, 0,
(LPARAM) (LPCTSTR) "No Phone Number");

EnableWindow(GetDlgItem(hwndDlg, IDC_DIAL), FALSE);
return;
}

// If we use the dialing rules, lets make canonical format.
// Canonical format is explained in the TAPI documentation and the
// string format needs to be followed very strictly.
if (SendDlgItemMessage(hwndDlg, IDC_USEDIALINGRULES,
BM_GETCHECK, 0, 0))
{
// First character *has* to be the plus sign.
szPreTranslatedNumber[0] = '+';
nPreTranslatedSize = 1;

// The country code *has* to be next.
// Country code was stored in the string with the country
// name and needs to be extracted at this point.
i = SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE,
CB_GETCURSEL, 0, 0);
SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE,
CB_GETLBTEXT, (WPARAM) i, (LPARAM) (LPCTSTR) szTempBuffer);

// Country code is at the end of the string, surounded by parens.
// This makes it easy to identify the country code.
i = strlen(szTempBuffer);
while(szTempBuffer[--i] != '(');

while(szTempBuffer[++i] != ')')
szPreTranslatedNumber[nPreTranslatedSize++] = szTempBuffer[i];

// Next is the area code.
i = SendDlgItemMessage(hwndDlg, IDC_AREACODE, WM_GETTEXT,
510, (LPARAM) (LPCTSTR) szTempBuffer);

// Note that the area code is optional. If it is included,
// then it has to be preceeded by *exactly* one space and it
// *has* to be surrounded by parens.
if (i)
nPreTranslatedSize +=
wsprintf(&szPreTranslatedNumber[nPreTranslatedSize],
" (%s)", szTempBuffer);

// There has to be *exactly* one space before the rest of the number.
szPreTranslatedNumber[nPreTranslatedSize++] = ' ';

// At this point, the phone number is appended to the
// canonical number. The next step is the same whether canonical
// format is used or not; just the prepended area code and
// country code are different.
}

SendDlgItemMessage(hwndDlg, IDC_PHONENUMBER, WM_GETTEXT,
510, (LPARAM) (LPCTSTR) szTempBuffer);

strcat(&szPreTranslatedNumber[nPreTranslatedSize], szTempBuffer);

dwDeviceID = SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
CB_GETCURSEL, 0, 0);

// Translate the address!
lpLineTranslateOutput = I_lineTranslateAddress(
lpLineTranslateOutput, dwDeviceID, SAMPLE_TAPI_VERSION,
szPreTranslatedNumber);

// Unable to translate it?
if (lpLineTranslateOutput == NULL)
{
SendDlgItemMessage(hwndDlg, IDC_CANONICALNUMBER, WM_SETTEXT, 0,
(LPARAM) NULL);
SendDlgItemMessage(hwndDlg, IDC_DIALABLENUMBER, WM_SETTEXT, 0,
(LPARAM) NULL);
SendDlgItemMessage(hwndDlg, IDC_DISPLAYABLENUMBER, WM_SETTEXT, 0,
(LPARAM) (LPCTSTR) "Invalid Phone Number or Area Code");

EnableWindow(GetDlgItem(hwndDlg, IDC_DIAL), FALSE);
return;
}

// Is the selected device useable with TapiComm?
if (g_dwDeviceID != MAXDWORD)
EnableWindow(GetDlgItem(hwndDlg, IDC_DIAL), TRUE);

// Fill the appropriate phone number controls.
SendDlgItemMessage(hwndDlg, IDC_CANONICALNUMBER, WM_SETTEXT, 0,
(LPARAM) (LPCTSTR) szPreTranslatedNumber);

SendDlgItemMessage(hwndDlg, IDC_DIALABLENUMBER, WM_SETTEXT, 0,
(LPARAM) ((LPSTR) lpLineTranslateOutput +
lpLineTranslateOutput -> dwDialableStringOffset));

SendDlgItemMessage(hwndDlg, IDC_DISPLAYABLENUMBER, WM_SETTEXT, 0,
(LPARAM) ((LPSTR) lpLineTranslateOutput +
lpLineTranslateOutput -> dwDisplayableStringOffset));

LocalFree(lpLineTranslateOutput);

}


//
// FUNCTION: void PreConfigureDevice(HWND, DWORD)
//
// PURPOSE:
//
// PARAMETERS:
// hwndDlg - handle to the current "Dial" dialog
// dwDeviceID - line device to be configured
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
// At one point, PreConfigureDevice used lineConfigDialog to
// configure the device. This has the unfortunate effect of configuring
// the device immediately, even if it is in use by another TAPI app.
// This can be really bad if data communications are already in
// progress (like with RAS).
//
// Now, PreConfigureDevice uses lineConfigDialogEdit to give the
// user the configuration UI, but it doesn't actually do anything to
// the line device. TapiComm stores the configuration information so
// that it can be set later, just before making the call.
//
//

void PreConfigureDevice(HWND hwndDlg, DWORD dwDeviceID)
{
long lReturn;
LPVARSTRING lpVarString = NULL;
DWORD dwSizeofVarString = sizeof(VARSTRING);

// If there isn't already any device configuration information,
// then we need to get some.
if (g_lpDeviceConfig == NULL)
{
do
{
lpVarString = (LPVARSTRING) CheckAndReAllocBuffer(
(LPVOID) lpVarString, dwSizeofVarString,
"PreConfigureDevice - lineGetDevConfig: ");

if (lpVarString == NULL)
return;

lReturn = lineGetDevConfig(dwDeviceID, lpVarString,
"comm/datamodem");

if (HandleLineErr(lReturn))
;
else
{
OutputDebugLineError(lReturn,
"lineGetDevCaps unhandled error: ");
LocalFree(lpVarString);
return;
}

if ((lpVarString -> dwNeededSize) > (lpVarString -> dwTotalSize))
{
dwSizeofVarString = lpVarString -> dwNeededSize;
lReturn = -1; // Lets loop again.
}
}
while (lReturn != SUCCESS);

g_dwSizeDeviceConfig = lpVarString -> dwStringSize;

// The extra byte allocated is in case dwStringSize is 0.
g_lpDeviceConfig = CheckAndReAllocBuffer(
g_lpDeviceConfig, g_dwSizeDeviceConfig+1,
"PreConfigureDevice - Allocate device config: ");

if (!g_lpDeviceConfig)
{
LocalFree(lpVarString);
return;
}

memcpy(g_lpDeviceConfig,
((LPBYTE) lpVarString + lpVarString -> dwStringOffset),
g_dwSizeDeviceConfig);
}

// Next make the lineConfigDialogEdit call.

// Note that we determine the initial size of the VARSTRING
// structure based on the known size of the existing configuration
// information. I make the assumption that this configuration
// information is very unlikely to grow by more than 5K or by
// more than 5 times. This is a *very* conservative number.
// We do *not* want lineConfigDialogEdit to fail just because there
// wasn't enough room to stored the data. This would require the user
// to go through configuration again and that would be annoying.

dwSizeofVarString = 5 * g_dwSizeDeviceConfig + 5000;

do
{
lpVarString = (LPVARSTRING) CheckAndReAllocBuffer(
(LPVOID) lpVarString, dwSizeofVarString,
"PreConfigureDevice - lineConfigDialogEdit: ");

if (lpVarString == NULL)
return;

lReturn = lineConfigDialogEdit(dwDeviceID, hwndDlg, "comm/datamodem",
g_lpDeviceConfig, g_dwSizeDeviceConfig, lpVarString);

if (HandleLineErr(lReturn))
;
else
{
OutputDebugLineError(lReturn,
"lineConfigDialogEdit unhandled error: ");
LocalFree(lpVarString);
return;
}

if ((lpVarString -> dwNeededSize) > (lpVarString -> dwTotalSize))
{
// We had been conservative about making sure the structure was
// big enough. Unfortunately, not conservative enough. Hopefully,
// this will not happen a second time because we are *DOUBLING*
// the NeededSize.
MessageBox(hwndDlg,
"Internal Error: Unable to set line configuration.\n"
"Please try again.",
"Oops", MB_OK);
dwSizeofVarString = (lpVarString -> dwNeededSize) * 2;
lReturn = -1; // Lets loop again.
}
}
while (lReturn != SUCCESS);

// Store the configuration information into a global structure
// so it can be set at a later time.
g_dwSizeDeviceConfig = lpVarString -> dwStringSize;
g_lpDeviceConfig = CheckAndReAllocBuffer(
g_lpDeviceConfig, g_dwSizeDeviceConfig+1,
"PreConfigureDevice - Reallocate device config: ");

if (!g_lpDeviceConfig)
{
LocalFree(lpVarString);
return;
}

memcpy(g_lpDeviceConfig,
((LPBYTE) lpVarString + lpVarString -> dwStringOffset),
g_dwSizeDeviceConfig);

LocalFree(lpVarString);
}


//
// FUNCTION: BOOL GetAddressToDial
//
// PURPOSE: Get an address to dial from the user.
//
// PARAMETERS:
// none
//
// RETURN VALUE:
// TRUE if a valid device and phone number have been entered by
// the user. FALSE if the user canceled the dialing process.
//
// COMMENTS:
//
// All this function does is launch the "Dial" dialog.
//
//

BOOL GetAddressToDial()
{
BOOL bRet;

UpdateStatusBar("Getting Number to Dial",1,0);
bRet = DialogBoxParam(hInst, "DialDialog", g_hDlgParentWindow,
DialDialogProc, 0);
g_hDialog = NULL;
g_hDlgParentWindow = g_hWndMainWindow;

if (bRet == FALSE)
UpdateStatusBar("Dial aborted",1,0);

return bRet;
}


//
// FUNCTION: DialDialogProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Dialog callback procedure for the dialing dialog
//
// PARAMETERS:
// hwndDlg - Dialog calling the callback.
// uMsg - Dialog message.
// wParam - uMsg specific.
// lParam - uMsg specific.
//
// RETURN VALUE:
// returns 0 - command handled.
// returns non-0 - command unhandled
//
// COMMENTS:
//
// This is the dialog to get the phone number and line device
// from the user. All the relavent information is stored in global
// variables to be used later if the dialog returns successfully.
//
//


BOOL CALLBACK DialDialogProc(
HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Static variables to store the information from last time the
// "Dial" dialog was displayed. That way the phone number can be
// typed once but used several times.

static char szCountryName[512] = "";
static char szAreaCode[256] = "Not Valid";
static char szPhoneNumber[512] = "Not Valid";
static DWORD dwUsedDeviceID = MAXDWORD;
static BOOL bUsedCountryAndArea = FALSE;
static BOOL bHistoryValid = FALSE;

switch(uMsg)
{
case WM_INITDIALOG:
{
DWORD dwCountryID = 0;

// Store the Dialog Window so it can be dismissed if necessary
g_hDialog = hwndDlg;

// This dialog should be parent to all dialogs.
g_hDlgParentWindow = hwndDlg;

// Initialize the Dialog Box. Lots to do here.

FillTAPILine(hwndDlg);
if (g_lpDeviceConfig)
{
LocalFree(g_lpDeviceConfig);
g_lpDeviceConfig = NULL;
}

// If there is a valid history, use it to initialize the controls.
if (bHistoryValid)
{
FillLocationInfo(hwndDlg, NULL, NULL, NULL);
FillCountryCodeList(hwndDlg, 0);

SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE, CB_SELECTSTRING,
(WPARAM) -1, (LPARAM) (LPCTSTR) szCountryName);

SendDlgItemMessage(hwndDlg, IDC_PHONENUMBER, WM_SETTEXT, 0,
(LPARAM) (LPCTSTR) szPhoneNumber);

SendDlgItemMessage(hwndDlg, IDC_USEDIALINGRULES,
BM_SETCHECK, (WPARAM) bUsedCountryAndArea, 0);

SendDlgItemMessage(hwndDlg, IDC_TAPILINE, CB_SETCURSEL,
g_dwDeviceID, 0);
}
else
{
FillLocationInfo(hwndDlg, NULL, &dwCountryID, szAreaCode);
FillCountryCodeList(hwndDlg, dwCountryID);
SendDlgItemMessage(hwndDlg, IDC_USEDIALINGRULES,
BM_SETCHECK, 1, 0);
}

SendDlgItemMessage(hwndDlg, IDC_AREACODE, WM_SETTEXT,
0, (LPARAM) (LPCTSTR) szAreaCode);

UseDialingRules(hwndDlg);
DisplayPhoneNumber(hwndDlg);
VerifyAndWarnUsableLine(hwndDlg);

return TRUE;
}

case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case IDC_TAPILINE:
if (HIWORD(wParam) == CBN_SELENDOK)
{
if (g_lpDeviceConfig)
{
LocalFree(g_lpDeviceConfig);
g_lpDeviceConfig = NULL;
}
DisplayPhoneNumber(hwndDlg);
VerifyAndWarnUsableLine(hwndDlg);
}
return TRUE;

case IDC_CONFIGURELINE:
{
DWORD dwDeviceID;
dwDeviceID = SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
CB_GETCURSEL, 0, 0);
PreConfigureDevice(hwndDlg, dwDeviceID);
DisplayPhoneNumber(hwndDlg);
return TRUE;
}

case IDC_COUNTRYCODE:
if (HIWORD(wParam) == CBN_SELENDOK)
DisplayPhoneNumber(hwndDlg);
return TRUE;

case IDC_AREACODE:
case IDC_PHONENUMBER:
if (HIWORD(wParam) == EN_CHANGE)
DisplayPhoneNumber(hwndDlg);
return TRUE;

case IDC_USEDIALINGRULES:
if (HIWORD(wParam) == BN_CLICKED)
{
UseDialingRules(hwndDlg);
DisplayPhoneNumber(hwndDlg);
}
return TRUE;

case IDC_LOCATION:
if (HIWORD(wParam) == CBN_CLOSEUP)
{
char szCurrentLocation[1024];
int nCurrentSelection;

nCurrentSelection = SendDlgItemMessage(hwndDlg,
IDC_LOCATION, CB_GETCURSEL, 0, 0);
SendDlgItemMessage(hwndDlg, IDC_LOCATION,
CB_GETLBTEXT, nCurrentSelection,
(LPARAM) (LPCTSTR) szCurrentLocation);

// If the user selected a 'location', make it current.
FillLocationInfo(hwndDlg, szCurrentLocation, NULL, NULL);
DisplayPhoneNumber(hwndDlg);
}
return TRUE;

case IDC_DIALINGPROPERTIES:
{
char szAddress[1024];
DWORD dwDeviceID;
long lReturn;

dwDeviceID = SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
CB_GETCURSEL, 0, 0);

SendDlgItemMessage(hwndDlg, IDC_CANONICALNUMBER,
WM_GETTEXT, 1023, (LPARAM) (LPCTSTR) szAddress);

lReturn = lineTranslateDialog(g_hLineApp, dwDeviceID,
SAMPLE_TAPI_VERSION, hwndDlg, szAddress);

if (lReturn != SUCCESS)
OutputDebugLineError(lReturn,"lineTranslateDialog: ");

// The user could have changed the default location, or
// added or removed a location while in the 'Dialing
// Properties' dialog. Refill the Location Info.
FillLocationInfo(hwndDlg, NULL, NULL, NULL);
DisplayPhoneNumber(hwndDlg);

return TRUE;
}

case IDCANCEL:
EndDialog(hwndDlg, FALSE);
return TRUE;

case IDC_DIAL:
{
// The Dial button has to be enabled and the line has
// to be currently usable to continue.
if (!(IsWindowEnabled((HWND)lParam) &&
VerifyAndWarnUsableLine(hwndDlg)))
return TRUE;

DisplayPhoneNumber(hwndDlg);

// Get the displayable and dialable numbers and store
// them in global variables to be used while dialing.
SendDlgItemMessage(hwndDlg, IDC_DISPLAYABLENUMBER,
WM_GETTEXT, 1023, (LPARAM) (LPCTSTR) g_szDisplayableAddress);

SendDlgItemMessage(hwndDlg, IDC_DIALABLENUMBER,
WM_GETTEXT, 1023, (LPARAM) (LPCTSTR) g_szDialableAddress);

// Store all the relavent information in static

// variables so they will be available the next time a 
// number is dialed.
SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE,
WM_GETTEXT, 511, (LPARAM) (LPCTSTR) szCountryName);

SendDlgItemMessage(hwndDlg, IDC_AREACODE,
WM_GETTEXT, 255, (LPARAM) (LPCTSTR) szAreaCode);

SendDlgItemMessage(hwndDlg, IDC_PHONENUMBER,
WM_GETTEXT, 511, (LPARAM) (LPCTSTR) szPhoneNumber);

bUsedCountryAndArea = (BOOL) SendDlgItemMessage(hwndDlg,
IDC_USEDIALINGRULES, BM_GETCHECK, 0, 0);

bHistoryValid = TRUE;

EndDialog(hwndDlg, TRUE);
return TRUE;
}


// This message is actually posted to the dialog from the
// lineCallbackFunc when it receives a
// LINEDEVSTATE_TRANSLATECHANGE message. Notify the user and
// retranslate the number. Also refill the Location Info
// since this could have been generated by a location change.
case IDC_CONFIGURATIONCHANGED:
{
FillLocationInfo(hwndDlg, NULL, NULL, NULL);
DisplayPhoneNumber(hwndDlg);

MessageBox(hwndDlg,
"Location Configuration has been changed.",
"Warning",MB_OK);

return TRUE;
}

// If we get a LINE_CREATE message, all that needs to be done
// is to reset this controls contents. The selected line
// won't change and no lines will be removed.
case IDC_LINECREATE:
{
FillTAPILine(hwndDlg);
return TRUE;
}

default:
break;
}

break;
}

// This dialog has the DS_CONTEXTMENU flag so that the right mouse
// button will send this message. Bring up the appropriate help page.
case WM_CONTEXTMENU:
{

if (hwndDlg != (HWND) wParam)
WinHelp ((HWND)wParam,
"TAPICOMM.HLP",
HELP_CONTEXTMENU,
(DWORD)(LPVOID) g_adwSampleMenuHelpIDs);
break;
}

// Bring up the appropriate help page.
case WM_HELP:
{
LPHELPINFO lphi;

lphi = (LPHELPINFO)lParam;
if ((lphi->iContextType == HELPINFO_WINDOW) &&
(hwndDlg != lphi->hItemHandle) &&
(lphi->iCtrlId < IDC_NOHELPCONTROLS))
{
WinHelp (lphi->hItemHandle,
"TAPICOMM.HLP",
HELP_WM_HELP,
(DWORD)(LPVOID) g_adwSampleMenuHelpIDs);
}

return TRUE;
}

default:
break;
}

return FALSE;
}