/*

					MDI Frame window handler
					
*/
					
#include "netfone.h"

//	Variables exported

HWAVEOUT hWaveOut = NULL;				// Wave output handle
HWAVEIN hWaveIn = NULL;                 // Wave input handle
int isWaveSound = FALSE;				// Input from wave file ?
int outputActive = FALSE;				// Is wave output open ?
int inputActive = FALSE;				// Is wave input open ?
int inputPaused = FALSE;				// Is input paused for output ?
int openConnections = 0;				// Number of open connections
int listeners = 0;						// Current wantsInput windows

int broadcasting = FALSE;				// Broadcasting to all connections ?
int bConferencing = FALSE;				// In conference mode ? -BCW 11/30/1997
int bEchoPackets  = FALSE;				// In echo mode ? -BCW 10/03/1998

static int outputTimeout = 0;			// Output release timeout
long outputPending = 0;                 // Output buffers in queue
long microsecondsPending = 0;			// Microseconds of samples in buffers on queue
double smoothedMicrosecondsPending = 0.0; // Smoothed microseconds pending in queue
int halfDuplexTransition = FALSE;		// Transitioning from output to input ?
int outputInShutdown = FALSE;			// Close output when last buffer returned
#ifdef HTML_HELP
DWORD HtmlHelpContext = 0;				// HTML Help context
#endif
gsm gsmh = NULL;						// GSM handle
#ifdef CELP_USE_CONTEXT
struct celp_context *celp_xp = NULL;	// Transmit CELP context pointer
#endif
rate_t xrate;							// Transmit 2X compression context
HCURSOR phoneCursor = NULL,
		boltCursor = NULL,
	    earCursor = NULL;				// Cursors
int voxmode = IDM_VOX_NONE;				// VOX mode
int breakinput = FALSE;					// break input audio stream for output
int compression = FALSE;				// 2X compression mode
int gsmcompress = TRUE;					// GSM compression mode
int adpcmcompress = FALSE;				// ADPCM compression mode
int lpccompress = FALSE;				// LPC compression mode
int lpc10compress = FALSE;				// LPC-10 compression mode
int celpcompress = FALSE;				// CELP compression mode
int robust = 1;							// Robust mode for LPC-10
int voxcompress = FALSE;				// VOXGSM compression mode
int protocolXmit = PROTOCOL_SPEAKFREE;	// Default protocol to send (!protocolAuto or new connection)
int protocolSent = PROTOCOL_SPEAKFREE;	// Protocol we're currently sending 
int protocolAuto = TRUE;				// Send same protocol as received from connection

public_key_package pk_package = PGP;	// Public key package

#ifdef MODEM
int modemEnable = FALSE;				// Modem connections enabled ?
char modemInitString[128] =				// Modem initialisation string
							"ATQ0V1E1S0=1";
char baudrate[12] = "19200";			// Baud rate
char commport[12] = "COM1";				// Communications port
int modemShowRant = TRUE;				// Show rant about Windows serial I/O support
int modemSessions = 0;					// Open sessions on modem
#endif

int alwaysBindSocket = FALSE;			// Bind output socket (WINSOCK bug work-around)
int useSendNotSendto = FALSE;			// Use send() to socket, not sendto()
int waNetNoConnect = FALSE;				// Don't connect(), use sendto()
int waNetUseSend = FALSE;				// Use send(), not sendto() always
int waNetMultiTTLisChar = FALSE;		// Argument to IP_MULTICAST_TTL setsockopt is char
int waNetNoOutOverflow = FALSE;			// Disable output overflow detection and recovery
int waNetNoMsgLoopIns = FALSE;			// Disable message loop insurance
int waNetSynchronousGetHostname = 2;	// Use gethostbyaddr(), not WASAsync variant
int waNetSynchronousGetHostnameAction = FALSE; // Action set by above variable

int waProtNoHeartbeat = FALSE;			// No heartbeat in Speak Freely protocol
int waProtUseLargerRTCPackets = FALSE;	// Use large packets with RTP protocol
int waProtNoVAT = FALSE;				// Disable auto-sensing of VAT protocol
int waProtNoRTP = FALSE;				// Disable auto-sensing of RTP protocol
int waProtNoRTCPCrypt = FALSE;			// Don't encrypt RTCP packets			 

int waAudioHalf = FALSE;				// Assume audio half-duplex; don't test
int waAudio11025 = FALSE;				// Assume audio 11025 samples/sec												
int waAudioRingMaxVolume = FALSE;		// Force maximum output volume for remote ring
int waAudioNoOutputRateAdjustment = FALSE;			// Disable automatic output rate adjustment based on queue length
static UINT waAudioInDevice = (UINT) WAVE_MAPPER;	// Default audio input device
static UINT waAudioOutDevice = (UINT) WAVE_MAPPER;	// Default audio output device

int halfDuplex = FALSE;					// Audio hardware is half-duplex
static int audioIs8Bit = FALSE;			// Nonzero if audio is 8 bit only
static int audioUse8Bit = FALSE;		// Use 8 bit audio
int netMaxSamples;						// Maximum samples network can send
int currentOutputRate = EXCHANGE_SAMPLE_RATE;	// Current output sample rate
static int currentInputLength;			// Current expected input buffer length
int currentInputSamples;				// Samples desired in current input buffers
int holped = FALSE;						// Help was invoked somewhere
static char ringFileName[MAX_PATH] = "";// Ring file name

//												Window Display Modes
static int lookWho_sTalking = FALSE;	// Restore from icon on new connection
int openOnAnswerMessage = FALSE;		// Restore from icon when new answering machine message arrives
static int hideWhenMinimised = FALSE;	// Hide taskbar icon when minimised
static int startMinimised = FALSE;		// Start minimised

#ifdef TRAY
static int lurkInTray = FALSE;			// Lurk in system tray when minimised ?
HWND trayIconWindow = NULL;				// Tray icon window
#endif

char lwl_s_server[MAX_PATH];			// Look Who's Listening server name
char lwl_s_email[80];					//		E-mail address
char lwl_s_fullname[80];				//		Full name
char lwl_s_phone[80];					//		Telephone number
char lwl_s_location[80];				//		Location name
int lwl_s_publish;						//		Publish in directory ?
int lwl_s_exact;						//		Exact match only ?

char lwl_a_server[MAX_PATH];			// Look Who's Listening query server
int lwl_a_exact;						//		Exact matches only

int lwl_t_published = FALSE;			// Directory listing published
int lwl_t_resend = 0;					// Time to update directory listing ?
static int lwl_t_diactive = FALSE;		// LWL dialogue up
int lwl_t_pending = FALSE;				// LWL server update pending ?
static int lwl_t_tock = 0;				// Mushy timer for LWL socket handling

unsigned long ssrc;			  			// RTP synchronisation source identifier
unsigned long timestamp; 	  			// RTP packet timestamp
unsigned short seq;			  			// RTP packet sequence number
unsigned long rtpdesrand;				// RTP DES random RTCP prefix
char *sdes = NULL;			  			// LWL SDES packet
int sdesl;					  			// LWL SDES packet length

int spurt = TRUE;						// Start of talk spurt flag	

char *vatid = NULL;			  			// VAT ID packet
int vatidl;					  			// VAT ID packet length
char *rtpsdes = NULL;			  		// RTP SDES packet
int rtpsdesl;					  		// RTP SDES packet length

HINSTANCE hInst;                        // The current instance handle
HACCEL hAccel;							// Accelerator table handle
HWND hwndMDIFrame;						// MDI frame  window handle
HWND hwndMDIClient;						// MDI client window handle
HWND hDlgPropeller = NULL;				// Propeller head modeless dialogue
HWND hDlgChat = NULL;					// Chat modeless dialogue
HWND hDlgConsole = NULL;				// Debug console modeless dialogue
HWND hDlgSpectral = NULL;				// Spectral display modeless dialogue
LPSTR commandLine = NULL;				// Command line from invocation
HWND hDlgAnswer = NULL;					// Answering machine modeless dialogue
LPSTR pszAppName;						// Application name
INT	tmAveCharWidth;						// TEXTMETRIC.tmAveCharWidth
INT	tmHeight;							// TEXTMETRIC.tmHeight

int rememberedConnections = 0;			// Number of remembered connections
LPSTR rememberedConnection[REMEMBER_CONNECTIONS];  // Remembered connections

UINT fileOpenHelpButton;				// File open help button message value
char *fileHelpKey = NULL;				// Help key for file open/save in progress

int multiMemberships = 0;				// Number of multicast group memberships
#ifdef IP_MAX_MEMBERSHIPS 
struct in_addr multiAddr[IP_MAX_MEMBERSHIPS]; // Multicast group IP numbers
LPSTR multiName[IP_MAX_MEMBERSHIPS];	// Multicast group names
#endif
int multiLoop = FALSE;					// Multicast loop-back mode
int multiBrainDead = FALSE;				// Multicast loop-back option not supported

UINT jitterBuf = 1000;					// Milliseconds to anti-jitter output
BOOL jitterPause = FALSE;				// Queueing packets for anti-jitter ?
static UINT jitterTimer = 0;			// Jitter timer
#define JITTER_TIMER_ID	6				// Timer ID for anti-jitter

//	Local variables

#define nWaveHeaders	4
static SOCKET sCommand = INVALID_SOCKET;	// Command socket
static SOCKET sControl = INVALID_SOCKET;	// RTP/VAT control port socket
static SOCKET lwlsock = INVALID_SOCKET;	// Look Who's Listening periodic update socket
struct auxSocket *asList = NULL;		// Auxiliary socket list
static int inputSoundBufferSize = 0;	// Wave input buffer size (sample space) in bytes
static LPWAVEHDR inWaveHeader[nWaveHeaders];// Pointers to wave input buffers
static int waveHeadersAllocated = 0;	// Number of allocated wave headers
static int createSuccessful = FALSE;	// Set once we've loaded .INI parameters
static int inputTerm = FALSE;			// Input terminating
static soundbuf receivedSoundBuffer;	// Sound buffer from network
#ifdef MODEM
static LPSTR modemSb = NULL;			// Modem sound buffer assembly area
#endif

static UINT bombTimer = 0;				// Timer for close bomb
static int closeBomb = FALSE;			// Trigger close when audio active
#define BOMB_TIMER_ID	7				// Close timebomb ID

u_long Lazarus = 0;						// Lazarus host ID
int LazarusLong = 0;					// If nonzero, anti-reincarnation timeout

//	Audio hardware configuration

int audioChannels = 1;			// Audio input channels

//#define HIGH_QUALITY

#ifdef HIGH_QUALITY
int samplesPerSecond = 22050;		// Sound sampling rate
#else
int samplesPerSecond = 8000;		// Sound sampling rate
#endif
int bytesPerSecond = 16000;		// Sample bytes per second
int sampleAlignment = 2;			// Sample frame size
int bitsPerSample = 16;			// Bits per sample

static char kS0[] = "0", kS1[] = "1";	// Frequently used string constants

//	Export current audio settings to About dialogue

int aboutInSamples = 0;					// Input samples per second
int aboutInBits;						// Input bits per sample
int aboutOutSamples = 0;				// Output samples per second
int aboutOutBits;						// Output bits per sample

//	Export various status information to Propeller Head dialogue

long packetsReceived = 0,				// Network packet traffic counters
	 packetsSent = 0,
	 inputPacketsLost = 0,				// Input packets lost due to half-duplex
	 outputPacketsLost = 0,				// Output packets lost due to net traffic
	 messageChecks = 0;					// Anti-lockup calls on DefaultMessageLoop
	 
int messageQueueSize = 120;				// Inbound message queue size

//	Network properties

int aboutUDPmax = 0;					// Maximum UDP packet size

//  Private constants

#define DESIRED_WINSOCK_VERSION 0x0101  // we'd like winsock ver 1.1...
#define MINIMUM_WINSOCK_VERSION 0x0001  // ...but we'll take ver 1.0

#define MODEM_INPUT_EVENT	9999		// Modem input socket status

//	MDI message forwarder

static LRESULT Frame_MDIMessageForwarder(HWND hwnd, UINT nMessage,
                                   WPARAM wParam, LPARAM lParam)
{
    return DefFrameProc(hwnd, hwndMDIClient, nMessage, wParam, lParam);

}

// PROPELLER  --  Update propeller head dialogue if it's displayed.

void propeller(int control, DWORD value)
{
	if (hDlgPropeller != NULL) {
		char s[80];
		
		wsprintf(s, Format(0), value);
		SetDlgItemText(hDlgPropeller, control, s);
	}
}
                                      
/* FINDCLIENTBYHOST  --  Find the connection window for a given
						 host address. */                                      
                                      
static HWND findClientByHost(LPSOCKADDR_IN paddr)
{
    HWND hwnd;

    hwnd = GetWindow(hwndMDIClient, GW_CHILD);

    while (hwnd != NULL) {
    	if ((WNDPROC) GetWindowLong(hwnd, GWL_WNDPROC) == ((WNDPROC) connectWndProc)) {
	        LPCLIENT_DATA pClientData = CLIENTPTR(hwnd);
	
	        if ((pClientData != NULL) &&
	            (pClientData->inetSock.sin_addr.s_addr == paddr->sin_addr.s_addr) &&
	            (pClientData->port == ntohs(paddr->sin_port))) {
	            return hwnd;
	        }
	    }
        hwnd = GetWindow(hwnd, GW_HWNDNEXT);
    }
    return NULL;
}

/*  GETACTIVECONNECTION  --  Get the active connection window.  If no
							 connection is active, NULL is returned.
							 A connection will not be returned unless
							 it has a valid client data structure.  */
							 
static HWND getActiveConnection(void)
{
	LRESULT mdiActive;
	HWND hwnd;

	if (hwndMDIClient != NULL) {
		mdiActive = SendMessage(hwndMDIClient, WM_MDIGETACTIVE,
								0, 0L);
		hwnd = (HWND) LOWORD(mdiActive); 
		if (hwnd != NULL &&
			((WNDPROC) GetWindowLong(hwnd, GWL_WNDPROC) == ((WNDPROC) connectWndProc)) &&
			CLIENTPTR(hwnd) != NULL) { 
			return hwnd;
		}
	}
	return NULL;							
}

#ifdef IP_MAX_MEMBERSHIPS

/*  MULTICASTJOIN  --  Join or drop multicast groups given in
					   the multicast table. */

void multicastJoin(HWND hwnd, int join)
{
	int i, what;
	struct ip_mreq mreq;
	struct auxSocket *s;
	static int recursed = 0;
	
	if (recursed > 0) {
		return;
	}
	recursed++;
	if (join) {
		int sstat;
		
		if (waNetMultiTTLisChar) {
			u_char loop;
			
			loop = (u_char) multiLoop;
			sstat = setsockopt(sCommand, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop,
					sizeof loop);
			if (sstat != -1) {
				sstat = setsockopt(sControl, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop,
						sizeof loop);
			}
		} else {
			int loop;
			
			loop = (int) multiLoop;
			sstat = setsockopt(sCommand, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop,
					sizeof loop);
			if (sstat != -1) {
				sstat = setsockopt(sControl, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop,
						sizeof loop);
			}
		}
		if (sstat == -1) {
	        int serr = WSAGetLastError();
			
			if (serr == WSAENOPROTOOPT) {
				//	Windows 95 and NT don't support IP_MULTICAST_LOOP
				multiBrainDead = TRUE;
			} else {
				/*	Since many Winsock implementations don't support multicast,
					disable the warning to prevent a natter every time we start
					up.  */ 
#ifdef MULTICAST_WARNING			            
		        MsgBox(hwnd, MB_ICONSTOP | MB_OK,
		                Format(8),
		                serr, SockerrToString(serr));
#endif		                
		    }
		}
		
		//	Now set the TTL for any auxiliary sockets
		
		s = asList;
		while (s != NULL) {
			if (s->asrefc > 0) {
				if (waNetMultiTTLisChar) {
					u_char loop;
					
					loop = (u_char) multiLoop;
					sstat = setsockopt(s->asdata, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop,
							sizeof loop);
					if (sstat != -1) {
						sstat = setsockopt(s->asctrl, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop,
								sizeof loop);
					}
				} else {
					int loop;
					
					loop = (int) multiLoop;
					sstat = setsockopt(s->asdata, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop,
							sizeof loop);
					if (sstat != -1) {
						sstat = setsockopt(s->asctrl, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop,
								sizeof loop);
					}
				}
				if (sstat != -1) {
					what = join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP; 
					mreq.imr_interface.s_addr = htonl(INADDR_ANY);
					for (i = 0; i < multiMemberships; i++) {
						mreq.imr_multiaddr.s_addr = multiAddr[i].s_addr;
						if (setsockopt(s->asdata, IPPROTO_IP, what,
								(char *) &mreq, sizeof mreq) == -1) {
				            int serr = WSAGetLastError();
					            
					        MsgBox(hwnd, MB_ICONSTOP | MB_OK,
					                Format(9),
					                (LPSTR) (join ? Format(10) : Format(11)), inet_ntoa(multiAddr[i]),
					                serr, SockerrToString(serr));
						} else {
							setsockopt(s->asctrl, IPPROTO_IP, what, (char *) &mreq, sizeof mreq);
						}
					}
				}
			}
			s = s->asnext;
		}		
	}
	what = join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP; 
	mreq.imr_interface.s_addr = htonl(INADDR_ANY);
	for (i = 0; i < multiMemberships; i++) {
		mreq.imr_multiaddr.s_addr = multiAddr[i].s_addr;
		if (setsockopt(sCommand, IPPROTO_IP, what,
				(char *) &mreq, sizeof mreq) == -1) {
            int serr = WSAGetLastError();
	            
	        MsgBox(hwnd, MB_ICONSTOP | MB_OK,
	                Format(9),
	                (LPSTR) (join ? Format(10) : Format(11)), inet_ntoa(multiAddr[i]),
	                serr, SockerrToString(serr));
		} else {
			setsockopt(sControl, IPPROTO_IP, what, (char *) &mreq, sizeof mreq);
		}
		if (!join && multiName[i] != NULL) {
			GlobalFreePtr(multiName[i]);
			multiName[i] = NULL;
		}	
	}
	recursed--;
}
#endif

/*  ISHALFDUPLEX  --  Try to open audio input and output simultaneously.
					  If it fails, mark the hardware half-duplex.  */
					  
static int isHalfDuplex(HWND hwnd)
{
    PCMWAVEFORMAT wfi;
	MMRESULT woo;
	int hdx = FALSE;

#ifdef POINTY_HEAD
{
	HGLOBAL h, h1;
	LPSTR p;

	h = GlobalAlloc(GPTR, 1024);
	p = GlobalLock(h);
	h1 = GlobalHandle(p);
	GlobalUnlockPtr(h);
	GlobalFree(h);
}
#endif
	
	if (waAudio11025) {
		samplesPerSecond = 11025;
		bytesPerSecond = samplesPerSecond * 2;
	}
    	
	wfi.wf.wFormatTag = WAVE_FORMAT_PCM;
	wfi.wf.nChannels = audioChannels;
	
	//	Input initialisation
    	
	while (TRUE) {
    	wfi.wf.nSamplesPerSec = samplesPerSecond;
    	wfi.wf.nAvgBytesPerSec = bytesPerSecond;
    	wfi.wf.nBlockAlign = sampleAlignment;
    	wfi.wBitsPerSample = bitsPerSample;
 		woo = waveInOpen(&hWaveIn, waAudioInDevice, (LPWAVEFORMATEX) &wfi,
 				0L, 0L, WAVE_FORMAT_QUERY);
 		if (bitsPerSample > 8 && woo == WAVERR_BADFORMAT) {
 			audioIs8Bit = TRUE;
 		} 
	 				
 		/* If our preferred mode (16 bit) isn't supported, try falling
 		   back to bottom-feeder 8 bit per sample mode. */			
	
 		if ((audioUse8Bit || woo == WAVERR_BADFORMAT) && (bitsPerSample > 8)) {
 			bitsPerSample /= 2;
 			sampleAlignment /= 2;
 			bytesPerSecond /= 2;	
	    	wfi.wf.nAvgBytesPerSec = bytesPerSecond;
	    	wfi.wf.nBlockAlign = sampleAlignment;
	    	wfi.wBitsPerSample = bitsPerSample;
	 		woo = waveInOpen(&hWaveIn, waAudioInDevice, (LPWAVEFORMATEX) &wfi,
	 				0L, 0L, WAVE_FORMAT_QUERY);
 		}
	 		
 		/* If we've failed to initialise in either 16 or 8 bit mode
 		   at 8000 samples per second, it's possible the sound card
 		   doesn't support any sampling mode below the Windows standard
 		   of 11025 samples per second.  Have another go-round and see
 		   if 11025 works. */
	 		
 		if (woo == WAVERR_BADFORMAT && samplesPerSecond == 8000) {
 			samplesPerSecond = 11025;
 			bitsPerSample = 16;
 			sampleAlignment = bitsPerSample / 8;
 			bytesPerSecond = samplesPerSecond * sampleAlignment;  
 		} else {
 			break;
 		}
 	} 
	if (woo != 0) {
    	MessageBox(hwnd, rstring(IDS_T_WAVE_RECORD_FORMAT_ERR),
	   		NULL, MB_OK | MB_ICONEXCLAMATION);
		return -1;
	}
    if ((woo = waveInOpen(&hWaveIn, waAudioInDevice,
		  (LPWAVEFORMATEX) &wfi, (DWORD) (UINT) hwnd, 0L, (DWORD) CALLBACK_WINDOW)) != 0) {
    	char et[MAXERRORLENGTH];
			    
    	waveInGetErrorText(woo, et, sizeof et);
    	MessageBox(hwnd, et, rstring(IDS_T_ERR_OPEN_WAVE_INPUT), MB_OK | MB_ICONEXCLAMATION);
		return -1;
    }
    aboutInBits = bitsPerSample;
    aboutInSamples = samplesPerSecond;
    
    /* If workaround is set, close input now and mark half-duplex
       without waiting for the open of output to fail. */
    
    if (waAudioHalf) {
    	waveInClose(hWaveIn);
    	hdx = halfDuplex = TRUE;
    }
    
    //	Output initialisation

	while (TRUE) {
 		woo = waveOutOpen(&hWaveOut, waAudioOutDevice, (LPWAVEFORMATEX) &wfi,
 								0L, 0L, WAVE_FORMAT_QUERY);
 		if (bitsPerSample > 8 && woo == WAVERR_BADFORMAT) {
 			audioIs8Bit = TRUE;
 		} 
	 				
 		/* If our preferred mode (16 bit, 11025 samples/second) isn't
 		   supported, try falling back to bottom-feeder 8 bit per sample
 		   mode. */			
	
 		if ((audioUse8Bit || woo == WAVERR_BADFORMAT) && bitsPerSample > 8) {
 			bitsPerSample /= 2;
 			sampleAlignment /= 2;
 			bytesPerSecond /= 2;	
	    	wfi.wf.nAvgBytesPerSec = bytesPerSecond;
	    	wfi.wf.nBlockAlign = sampleAlignment;
	    	wfi.wBitsPerSample = bitsPerSample;
	 		woo = waveOutOpen(&hWaveOut, waAudioOutDevice, (LPWAVEFORMATEX) &wfi,
	 				0L, 0L, WAVE_FORMAT_QUERY);
 		}
	 		
 		/* If we've failed to initialise in either 16 or 8 bit mode
 		   at 8000 samples per second, it's possible the sound card
 		   doesn't support any sampling mode below the Windows standard
 		   of 11025 samples per second.  Have another go-round and see
 		   if 11025 works. */
	 		
 		if (woo == WAVERR_BADFORMAT && samplesPerSecond == 8000) {
 			samplesPerSecond = 11025;
 			bitsPerSample = 16;
 			sampleAlignment = bitsPerSample / 8;
 			bytesPerSecond = samplesPerSecond * sampleAlignment;  
 		} else {
 			break;
 		}
 	} 
 		
	if (woo != 0) {				
    	char et[MAXERRORLENGTH];
	    	
    	waveOutGetErrorText(woo, et, sizeof et);
    	MessageBox(hwnd, et, rstring(IDS_T_WAVE_PLAY_FORMAT_ERR),
	   		MB_OK | MB_ICONEXCLAMATION);
    	hdx = -1;
		goto FatalAudioExit;
	}
    if ((woo = waveOutOpen(&hWaveOut, waAudioOutDevice,
		  (LPWAVEFORMATEX) &wfi, (DWORD) (UINT) hwnd, 0, (DWORD) CALLBACK_WINDOW)) != 0) {
    	char et[MAXERRORLENGTH];
	    	
    	/* The next line looks wrong, doesn't it?  But I've seen drivers
    	   for half-duplex sound boards that return NOTSUPPORTED instead
    	   of ALLOCATED when you try to open input and output at the
    	   same time. */
		    
	    if (!waAudioHalf && (woo == MMSYSERR_ALLOCATED || woo == MMSYSERR_NOTSUPPORTED)) {
			hdx = halfDuplex = TRUE;
			waveInClose(hWaveIn);
			if (waveOutOpen(&hWaveOut, waAudioOutDevice,
		  			(LPWAVEFORMATEX) &wfi, (DWORD) (UINT) hwnd, 0,
		  			(DWORD) CALLBACK_WINDOW) == 0) {
			    aboutOutBits = bitsPerSample;
			    aboutOutSamples = samplesPerSecond;
			    waveOutClose(hWaveOut);
			    goto HdxAudioExit;						  		
		  	}
	    } else {	
	    	waveOutGetErrorText(woo, et, sizeof et);
	        MessageBox(hwnd, et, rstring(IDS_T_ERR_OPEN_WAVE_OUTPUT),
				MB_OK | MB_ICONEXCLAMATION);
			hdx = -1;
	    }
	} else {
	    aboutOutBits = bitsPerSample;
	    aboutOutSamples = samplesPerSecond;
		waveOutClose(hWaveOut);
	}
	
FatalAudioExit:
	if (!waAudioHalf) {
    	waveInClose(hWaveIn);
    }
HdxAudioExit:
    return hdx;
}					  

/*	WAVEOUTSHUTDOWN  --  Shutdown wave audio output, if open.  */
						 
static void waveOutShutdown(void)
{
	if (outputActive) {
		V waveOutReset(hWaveOut);
		if (outputPending) {
			outputInShutdown = TRUE;
	    	propUpdateAudio();
		} else {
			V waveOutClose(hWaveOut);
			outputActive = FALSE;
	    	propUpdateAudio();
		}
	}
}
/*	INPUTSAMPLECOUNT  --  Calculate best sample count for an audio input
						  buffer based on the current compression
						  modes and the characteristics of the audio
						  hardware.  */
						   
int inputSampleCount(void)
{
	int l = 0;
	
	switch (protocolSent) {
		case PROTOCOL_SPEAKFREE:	
			l = lpc10compress ? (180 * (compression ? 20 : 10)) :
					(celpcompress ? (compression ? 3840 : 1920) :
						(lpccompress ? (compression ? LPC_FRAME_SIZE * 20 : LPC_FRAME_SIZE * 10) : 
							((gsmcompress || voxcompress) ?
								(compression ? 3200 : 1600) :
								((512 - (sizeof(soundbuf) - BUFL)) * (compression ? 2 : 1)))));
			if (adpcmcompress) {
				l *= 2;
				l -= 4;				  	// Leave room for state at the end
			}
			break;
			
		case PROTOCOL_RTP:
			if (waProtUseLargerRTCPackets) {
				l = lpccompress ? (LPC_FRAME_SIZE * 10) :
						(gsmcompress ? 1600 : 480);
			} else {
				l = (gsmcompress | lpccompress | adpcmcompress) ? (160 * 4) : 480;
			}
			break;
				
		case PROTOCOL_VAT:
			l = (gsmcompress | lpccompress | adpcmcompress) ? (160 * 4) : 320;
			break;	
	}
	Assert((l * (bitsPerSample / 8)) < BUFL);	// Make sure BUFL is large enough for raw sample block
	return l;
}						  													 

/*	INPUTBUFFERLENGTH  --  Calculate best length in bytes for an audio input
						   buffer based on the current compression
						   modes and the characteristics of the audio
						   hardware.  */
						   
static int inputBufferLength(void)
{
	return (bitsPerSample / 8) * inputSampleCount();
}						  													 

/*	STARTWAVEINPUT  --  Activate wave audio input.  Returns TRUE
						if successful, FALSE if no input will be
						forthcoming for some twiddley reason or
						another.  The window handle is simply used
						as the parent for the message boxes announcing
						the bad news.  */

int startWaveInput(HWND hwnd)
{
    //	Attempt to initialise the audio input port
    
    if (!inputActive) {
    	int i;
	    PCMWAVEFORMAT wfi;    
    	MMRESULT woo;
    	
    	wfi.wf.wFormatTag = WAVE_FORMAT_PCM;
    	wfi.wf.nChannels = audioChannels;
    	
    	while (TRUE) {
	    	wfi.wf.nSamplesPerSec = samplesPerSecond;
	    	wfi.wf.nAvgBytesPerSec = bytesPerSecond;
	    	wfi.wf.nBlockAlign = sampleAlignment;
	    	wfi.wBitsPerSample = bitsPerSample;
	 		woo = waveInOpen(&hWaveIn, waAudioInDevice, (LPWAVEFORMATEX) &wfi,
	 				0L, 0L, WAVE_FORMAT_QUERY);
	 		if (bitsPerSample > 8 && woo == WAVERR_BADFORMAT) {
	 			audioIs8Bit = TRUE;
	 		} 
	 				
	 		/* If our preferred mode (16 bit) isn't supported, try falling
	 		   back to bottom-feeder 8 bit per sample mode. */			
	
	 		if ((audioUse8Bit || woo == WAVERR_BADFORMAT) && (bitsPerSample > 8)) {
	 			bitsPerSample /= 2;
	 			sampleAlignment /= 2;
	 			bytesPerSecond /= 2;	
		    	wfi.wf.nAvgBytesPerSec = bytesPerSecond;
		    	wfi.wf.nBlockAlign = sampleAlignment;
		    	wfi.wBitsPerSample = bitsPerSample;
		 		woo = waveInOpen(&hWaveIn, waAudioInDevice, (LPWAVEFORMATEX) &wfi,
		 				0L, 0L, WAVE_FORMAT_QUERY);
	 		}
	 		
	 		/* If we've failed to initialise in either 16 or 8 bit mode
	 		   at 8000 samples per second, it's possible the sound card
	 		   doesn't support any sampling mode below the Windows standard
	 		   of 11025 samples per second.  Have another go-round and see
	 		   if 11025 works. */
	 		
	 		if (woo == WAVERR_BADFORMAT && samplesPerSecond == 8000) {
	 			samplesPerSecond = 11025;
	 			bitsPerSample = 16;
	 			sampleAlignment = bitsPerSample / 8;
	 			bytesPerSecond = samplesPerSecond * sampleAlignment;  
	 		} else {
	 			break;
	 		}
	 	} 
		if (woo != 0) {
        	MessageBox(hwnd, rstring(IDS_T_WAVE_RECORD_FORMAT_ERR),
		   		NULL, MB_OK | MB_ICONEXCLAMATION);
			return FALSE;
    	}
    	for (i = 0; i < 2; i++) {
		    if ((woo = waveInOpen(&hWaveIn, waAudioInDevice,
				  (LPWAVEFORMATEX) &wfi, (DWORD) (UINT) hwndMDIFrame, 0L, (DWORD) CALLBACK_WINDOW)) != 0) {
		    	char et[MAXERRORLENGTH];
			    
	    	
		    	/* The next line looks wrong, doesn't it?  But I've seen drivers
		    	   for half-duplex sound boards that return NOTSUPPORTED instead
		    	   of ALLOCATED when you try to open input and output at the
		    	   same time. */
		    
			    if (i == 0 && outputActive && (woo == MMSYSERR_ALLOCATED || 
			    		woo == MMSYSERR_NOTSUPPORTED)) {
			    
			    	/* Okay, the preponderance of evidence points at our
			    	   machine being burdened with a half-duplex audio
			    	   device--one where the fact that we're playing audio
			    	   prevents from from simultaneously receiving it.
			    	   This is bad.  Let's proceed as we'd have done on
			    	   the 80 metre band in 1935--mute the receiver and
			    	   blast on our carrier regardless. */
			    	
			    	V waveOutReset(hWaveOut);
			    	halfDuplex = TRUE;			// Indicate wave output swiped
					if (outputPending) {
						halfDuplexTransition = TRUE;
	    				propUpdateAudio();
						return TRUE;
					} else {
						V waveOutClose(hWaveOut);
						outputActive = FALSE;
	    				propUpdateAudio();
					}
			    	continue;   
			    }	
		    	waveInGetErrorText(woo, et, sizeof et);
	        	MessageBox(hwnd, et,
			   		rstring(IDS_T_ERR_OPEN_WAVE_INPUT), MB_OK | MB_ICONEXCLAMATION);
				return FALSE;
		    }
		    break;
	    }
	    
		/*	Since the user is allowed to change the sample size
			and compression modes on the fly, but the audio input
			buffers are used for the entire run of the program, we
			allocate them for the worst-case scenario: the most extreme
			compression mode plus 2X compression.  We've already
			established whether the hardware can run at 8000 samples
			per second, so only allocate the larger buffer needed for
			11025 sample per second hardware if it's actually required.
			We determine the worst-case size based on BUFL, which specifies
			the largest sound buffer size in samples, which alredy takes into
			account maximum buffer size and compression.  Note tha BUFL
			already takes into account the possibility that samples may
			be two bytes, as is necessary when dealing with CODECs which
			require 16 bit PCM samples.  */
			
	    inputSoundBufferSize = (samplesPerSecond == EXCHANGE_SAMPLE_RATE) ? BUFL :
									(((BUFL + (EXCHANGE_SAMPLE_RATE - 1)) * 11025) /
											EXCHANGE_SAMPLE_RATE);
	    
	    /*	Now allocate and prepare the sound input buffers.  Don't
	    	you just love the code vomit Windows' inability to free
	    	resources when a program terminates creates?  */
	    
	    currentInputLength = inputBufferLength();
	    currentInputSamples = inputSampleCount();
	    Assert(currentInputLength <= inputSoundBufferSize);
	    			
	    for (i = 0; i < nWaveHeaders; i++) {
	    	inWaveHeader[i] = NULL;
	    }
	    for (i = 0; i < nWaveHeaders; i++) {
			inWaveHeader[i] = (LPWAVEHDR) GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
												sizeof(WAVEHDR));
			if (inWaveHeader[i] == NULL) {
				int j;
				
				for (j = i - 1; j >= 0; j++) {
					waveInUnprepareHeader(hWaveIn, inWaveHeader[j], sizeof(WAVEHDR));
					GlobalFreePtr(inWaveHeader[j]->lpData);
					GlobalFreePtr(inWaveHeader[j]);
					inWaveHeader[j] = NULL;
				}
	        	MessageBox(hwnd, rstring(IDS_T_INPUT_HEADER_ERR),
			   		NULL, MB_OK | MB_ICONEXCLAMATION);
			   	waveInClose(hWaveIn);
				return FALSE;
			}
			
			/*	Allocate the wave input buffer, reserving space at the end for
				the int where we stuff the number of microseconds it will take
				to play this buffer at the nominal output rate.  */
				
			inWaveHeader[i]->lpData = (LPSTR) GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
				(DWORD) (inputSoundBufferSize + sizeof(int)));

			if (inWaveHeader[i]->lpData == NULL) {
				int j;
				
				GlobalFreePtr(inWaveHeader[i]);
				inWaveHeader[i] = 0;
				for (j = i - 1; j >= 0; j++) {
					waveInUnprepareHeader(hWaveIn, inWaveHeader[j], sizeof(WAVEHDR));
					GlobalFreePtr(inWaveHeader[j]->lpData);
					GlobalFreePtr(inWaveHeader[j]);
					inWaveHeader[j] = NULL;
				}

	        	MessageBox(hwnd, rstring(IDS_T_INPUT_BUFFER_ERR),
			   		NULL, MB_OK | MB_ICONEXCLAMATION);
			   	waveInClose(hWaveIn);
				return FALSE;
			}
			
			inWaveHeader[i]->dwBufferLength = currentInputLength;
			inWaveHeader[i]->dwFlags = 0;
			waveInPrepareHeader(hWaveIn, inWaveHeader[i], sizeof(WAVEHDR));
	    }
	    waveHeadersAllocated = nWaveHeaders; 
	    
		for (i = 0; i < nWaveHeaders; i++) {
			waveInAddBuffer(hWaveIn, inWaveHeader[i], sizeof(WAVEHDR)); 
		}
		inputTerm = FALSE;
	    waveInStart(hWaveIn);
	    inputActive = TRUE;
	    aboutInSamples = samplesPerSecond;
	    aboutInBits = bitsPerSample;
	    propUpdateAudio();
    }
    return TRUE;
}
                                      
/*  TERMINATEWAVEINPUT  --  Shut down wave input and release all
							resources associated with it.  */
							
void terminateWaveInput(void)
{
	if (inputActive) {
		int i;
		
		inputTerm = TRUE;
		waveInReset(hWaveIn);
#ifdef OLDWAY		
		for (i = 0; i < nWaveHeaders; i++) {
			if (inWaveHeader[i] != NULL) {
				waveInUnprepareHeader(hWaveIn, inWaveHeader[i], sizeof(WAVEHDR));
				if (inWaveHeader[i]->lpData != NULL) {
					GlobalFreePtr(inWaveHeader[i]->lpData);
				}
				GlobalFreePtr(inWaveHeader[i]);
				inWaveHeader[i] = NULL;
			}
		}
#else		
		for (i = 0; i < nWaveHeaders; i++) {
			inWaveHeader[i] = NULL;
		}
#endif		
//		waveInClose(hWaveIn);
//		hWaveIn = NULL;
//		inputActive = FALSE;
//	    propUpdateAudio();
#ifdef TRYTHIS
		while (inputActive) {
			MSG msg;
			
			while (GetMessage(&msg, NULL, 0, 0)) {
				extern void MessageLoop(MSG *pmsg);
				
				MessageLoop(&msg);
		  	}
		}
#endif		
	}
}

/*	REMEMBERNEWCONNECTION  --  Add a connection file name to the list
							   of remembered connections.  If it's
							   already in the list, promote it to the
							   first item.  */
							   
static void rememberNewConnection(LPSTR connectionFile)
{
	int i;
	LPSTR hostSave;
	    	
    /*	See if the connection file already appears in the list
    	of recent connections.  If so, delete it from the list.  */
		    
    hostSave = GlobalAllocPtr(GPTR, strlen(connectionFile) + 1);
	strcpy(hostSave, connectionFile);
			
	/*	Careful!  Be sure not to reference connectionFile beyond
		this point, since if we're re-opening a remembered
		connection the following loop may (in fact almost certainly
		will) cause it to be deallocated. */
				 
    if (hostSave != NULL) {
		for (i = 0; i < rememberedConnections; i++) {
			if (_stricmp(hostSave, rememberedConnection[i]) == 0) {
	        	int j;
					
				GlobalFreePtr(rememberedConnection[i]);
				for (j = i + 1; j < rememberedConnections; j++) {
					rememberedConnection[j - 1] = rememberedConnection[j];	
				}
				rememberedConnections--;
			}
		}
				
		//	Push the list of remembered connections and add this one
				
		if (rememberedConnections == REMEMBER_CONNECTIONS) {
			GlobalFreePtr(rememberedConnection[REMEMBER_CONNECTIONS - 1]);
			rememberedConnections--;
		}
		for (i = REMEMBER_CONNECTIONS - 1; i >= 1; i--) {
			rememberedConnection[i] = rememberedConnection[i - 1];		
		}
		rememberedConnection[0] = hostSave;
		rememberedConnections++;
	}	    		
}							   
							                                      
/*  NEWCONNECTION  --  Initiate a connection to a given named
					   host.  If there's already a connection
					   to this host active, activate its window.  */
							
VOID newConnection(HWND hwnd, LPSTR connectionFile, LPSTR knownHost)
{
    CHAR szHostName[MAX_HOST];
    SOCKADDR_IN sockHost;
#define addrHost sockHost.sin_addr
    LPCLIENT_DATA pClientData = NULL;
    HWND hwndClient;
    unsigned short port;
    char *cg;

    //  Prompt the user for a new host

	if (connectionFile == NULL) {
		if (knownHost == NULL) {
		    if (!newHostDialogue(hwndMDIFrame, szHostName, &addrHost, &port)) {
		        return;
		    }
		} else {
	    	char *cp;
	    	long iport;
		    char cb[MAX_HOST];
			
			strcpy(cb, knownHost);
		    if ((cp = strchr(cb, '/')) != NULL ||
		    	(cp = strchr(cb, ':')) != NULL) {
		    	
		    	iport = atol(cp + 1);
		    	if (iport <= 0) {
			        MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(72), *cp); 
			        return;
		    	}
		    	port = (unsigned short) iport;
		    	*cp = 0;
		    } else {
		    	port = NETFONE_COMMAND_PORT;
		    }
		    
			addrHost.s_addr = inet_addr(cb);
			if (addrHost.s_addr != INADDR_NONE) {
				wsprintf(szHostName, Format(61), (LPSTR) cb);
			} else {
			
				//	If it didn't parse as an IP address, try it as a domain name
			
				LPHOSTENT h = gethostbyname(cb);
				
				if (h == NULL) {
		            int serr = WSAGetLastError();
							            
			        MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(41),
			                cb, serr, SockerrToString(serr));
			        return;
				} else {
					struct in_addr newaddr;
					
					newaddr = *((LPIN_ADDR) h->h_addr);
					memcpy(&addrHost.s_addr, (h->h_addr), sizeof addrHost.s_addr);
					strcpy(szHostName, cb);
				}
			}
		}
	} else {
		char inetAddr[64];
		
		/*	If the connection file contains a host name and IP address
			(which will be the case for any file we write), use them as
			given.  If only a host name is present, try to find the IP
			address with gethostbyname().  This is primarily intended for
			programs which wish to launch Speak Freely to contact a host
			with known name but unknown IP address.  I'm sure the next turn
			of the screw will have us doing it the other way around as
			well.  */
		
		cg = rstring(IDS_PF_HOST);
		if (GetPrivateProfileString(cg, rstring(IDS_PF_HOST_NAME), "", szHostName,
					sizeof szHostName, connectionFile) == 0) {
	        	MessageBox(hwnd, rstring(IDS_T_CONN_PROFILE_INVALID), connectionFile,
			   		MB_OK | MB_ICONEXCLAMATION);
			   	return;
		} else {
			if (GetPrivateProfileString(cg, rstring(IDS_PF_NETADDR), "", inetAddr,
					sizeof inetAddr, connectionFile) == 0 ||
				(addrHost.s_addr = inet_addr(inetAddr)) == INADDR_NONE) {
				LPHOSTENT h = gethostbyname(szHostName);
				
				if (h == NULL) {
		            int serr = WSAGetLastError();
							            
			        MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(41),
			                (LPSTR) szHostName,
			                serr, SockerrToString(serr));
			        return;
				} else {
					struct in_addr newaddr;
					
					newaddr = *((LPIN_ADDR) h->h_addr);
					memcpy(&addrHost.s_addr, (h->h_addr),
						sizeof addrHost.s_addr);
				}
			}
			port = GetPrivateProfileInt(cg, rstring(IDS_PF_PORT_NUMBER),
					NETFONE_COMMAND_PORT, connectionFile);
		} 
	}
    
    /*	See if there's already a client window communicating with
    	this host.  Use equality of IP number and port as the criterion to
    	prevent spoofing due to aliases. */
    
    sockHost.sin_port = htons(port);	
    hwndClient = findClientByHost(&sockHost);
    
    if (hwndClient != NULL) {
    	if (IsIconic(hwndClient)) {
			FORWARD_WM_MDIRESTORE(hwndMDIClient, hwndClient, SendMessage);
    	}
		BringWindowToTop(hwndClient);
		pClientData = CLIENTPTR(hwndClient);
    } else {
	
	    //  Allocate a new connection structure
	
	    pClientData = (LPCLIENT_DATA) GlobalAllocPtr(GPTR, sizeof(CLIENT_DATA));
	
	    if (pClientData != NULL) {
		
		    //  Initialize the connection descriptor
		    
		    memset(pClientData, 0, sizeof(CLIENT_DATA));
		    pClientData->dwType = WINDOW_TYPE_CLIENT;
		    pClientData->wantsInput = FALSE;
		    pClientData->broadcastBeginTime = GetTickCount();
		    pClientData->broadcastEnd = FALSE;
		    pClientData->state = Embryonic;
		    pClientData->sReply = pClientData->sControl = INVALID_SOCKET;
		    pClientData->timeout = -1;		// Connection with local origin is immortal
		    pClientData->sendSDEStimer = 0;
		    pClientData->buttonUpTimer = FALSE;
		    pClientData->inetSock.sin_addr.s_addr = addrHost.s_addr;
			pClientData->localLoopback = IS_LOCALHOST(addrHost.s_addr);
		    pClientData->modemConnection = (addrHost.s_addr == 0);
		    pClientData->hFile = HFILE_ERROR;
		    pClientData->multicast_scope = 1;
		    pClientData->szFile[0] = '\0';
	    	pClientData->face_stat = FSinit;
			pClientData->protocol = PROTOCOL_UNKNOWN;
			pClientData->llhead = pClientData->lltail = NULL;
			pClientData->uname = NULL;
			pClientData->email[0] = 0;
			pClientData->port = ntohs(sockHost.sin_port);
			pClientData->auxSock = NULL;
			pClientData->d_celp_ctx = NULL;
	    	memcpy(pClientData->session_id, "\221\007\311\201", 4);
	    	pClientData->face_bmp = NULL;
		    strcpy(pClientData->szHost, szHostName);
		} else {
	        return;
	    }
	
	    //  Create the connection child window
	
	    hwndClient = createNewConnection(pClientData);
	
	    if (hwndClient == NULL) {
	        GlobalFreePtr(pClientData);
	        return;
	    }
	}	    
	    
    /*	If the connection was defined in a file, restore the
    	parameters to those saved in the file.  */
	    	
    if (connectionFile != NULL) {
	    	    	
    	strcpy(pClientData->connectionFileName, connectionFile); 
	    
	    cg = rstring(IDS_PF_DEBUG);			
    	pClientData->debugging = GetPrivateProfileInt(cg,
    		rstring(IDS_PF_DEBUGGING), FALSE, connectionFile);	
    	pClientData->loopback = GetPrivateProfileInt(cg,
    		rstring(IDS_PF_LOOPBACK), FALSE, connectionFile);
	    
	    cg = rstring(IDS_PF_MULTICAST);			
    	pClientData->multicast_scope = GetPrivateProfileInt(cg,
    		rstring(IDS_PF_SCOPE), 1, connectionFile);
    	
    	cg = rstring(IDS_PF_ENCRYPTION);	
    	pClientData->saveKeys = GetPrivateProfileInt(cg,
    		rstring(IDS_PF_SAVE_KEYS), FALSE, connectionFile);
		GetPrivateProfileString(cg, rstring(IDS_PF_PGP_USER_NAMES), "",
			pClientData->opgpUserList,
			sizeof pClientData->opgpUserList, connectionFile);	    	
    	if (pClientData->saveKeys) {
			GetPrivateProfileString(cg, rstring(IDS_PF_DES_KEY), "",
				pClientData->desKeyString,
				sizeof pClientData->desKeyString, connectionFile);	    	
			GetPrivateProfileString(cg, rstring(IDS_PF_IDEA_KEY), "",
				pClientData->ideaKeyString,
				sizeof pClientData->ideaKeyString, connectionFile);	    	
			GetPrivateProfileString(cg, rstring(IDS_PF_BLOWFISH_KEY), "",
				pClientData->blowfishKeyString,
				sizeof pClientData->blowfishKeyString, connectionFile);	    	
			GetPrivateProfileString(cg, rstring(IDS_PF_AES_KEY), "",
				pClientData->aesKeyString,
				sizeof pClientData->aesKeyString, connectionFile);	    	
    		pClientData->aesKeyHex = GetPrivateProfileInt(cg,
    			rstring(IDS_PF_AES_KEY_HEX), FALSE, connectionFile);
			GetPrivateProfileString(cg, rstring(IDS_PF_KEY_FILE), "",
				pClientData->otpFileName,
				sizeof pClientData->otpFileName, connectionFile);
    	}
    	
    	//	Initialise settings derived from saved parameters

		if (!makeInternalEncryptionKeys(hwndClient, pClientData)) {
			pClientData->otpFileName[0] = 0;	
		}
	    	
    	/*	Caution: don't reference connectionFile anywhere after
    		the call to rememberNewConnection.  If we're re-opening
    		a remembered connection, the call may release it.  */
	    	
    	rememberNewConnection(connectionFile);
    }
#undef addrHost	
}

#ifdef CRYPTO

/*	PGPSETSESSIONKEY  --  If needed, invoke PGP to decrypt the session
						  key and set it in effect for PGP encrypted
						  packets.  */
						  
static void pgpSetSessionKey(HWND hwnd, LPCLIENT_DATA conn, soundbuf *d)
{
	char cmd[MAX_PATH + 40], kmd[16];
	HFILE kfile;
	struct MD5Context md5c;

	MD5Init(&md5c);
	MD5Update(&md5c, d->buffer.buffer_val, (unsigned) d->buffer.buffer_len);
	MD5Final(kmd, &md5c);

	if (memcmp(conn->pgpkeymd5, kmd, 16) != 0) {
		char td[MAX_PATH];

		GetTempPath(sizeof td, td);

		conn->pgpkey[0] = FALSE;
		memcpy(conn->pgpkeymd5, kmd, 16);
		GetTempFileName(
						td
						 , "PK", 0, conn->pgpFileName);

        kfile = _lcreat(conn->pgpFileName, 0);
		if (kfile == HFILE_ERROR) {
            MessageBox(hwnd, rstring(IDS_T_OPEN_SESSION_KEY_FILE_ERR),
            	rstring(IDS_T_PGP_KEY_ERR_TITLE), MB_ICONEXCLAMATION | MB_OK);
		} else {
			UINT execStat = 0;
		
			_lwrite(kfile, d->buffer.buffer_val, (UINT) d->buffer.buffer_len);
			_lclose(kfile);
			
			/*	First try to run PGP via the PIF in our own directory.  This
				guarantees it's run with the modes we've chosen, such as
				in a window rather than full-screen.  */
			
			if (GetModuleFileName(hInst, cmd, sizeof cmd) > 0) {
				char *cp = cmd + strlen(cmd);
				
				while (cp >= cmd && *cp != '\\' && *cp != ':') {
					cp--;
				}
				cp[1] = 0;
				if (pk_package == GPG) {
					strcpy(td, conn->pgpFileName);
					td[strlen(td) - 4] = 0;
					wsprintf(cmd + strlen(cmd), Format(83), td, conn->pgpFileName);
				} else {                     
					wsprintf(cmd + strlen(cmd), Format(52), conn->pgpFileName);
				}
//cPrintf("CMD: %s", cmd);
				execStat = WinExec(cmd, SW_SHOW); 
			}
//cPrintf("execStat = %d", execStat);

			/*	If that didn't work, attempt to run PGP by straight path
				search using the default modes.  */
				
			if (execStat < 32) {                       
				if (pk_package == GPG) {
            		wsprintf(cmd, Format(84), td, conn->pgpFileName);
				} else {
            		wsprintf(cmd, Format(1), conn->pgpFileName);
            	}
//cPrintf("CMD: %s", cmd);
            	execStat = WinExec(cmd, SW_SHOW);
//cPrintf("execStat = %d", execStat);
            }
            
            //	Set timer to poll for completion of decoding
            
            if (execStat >= 32) {
	            conn->pgpFileName[strlen(conn->pgpFileName) - 4] = 0;
	            SetTimer(hwnd, 3, 1000, NULL);
	        } else {
	        	wsprintf(cmd + strlen(cmd), Format(51), execStat);
	        	MessageBox(hwnd, cmd, rstring(IDS_T_CANT_INVOKE_PGP),
			   		MB_OK | MB_ICONEXCLAMATION);
	        }
		}
	}
}
#endif 						  

//	onCreate  --  Initialise frame window when newly created

static BOOL onCreate(HWND hwnd, CREATESTRUCT *pCreateStruct)
{
    CLIENTCREATESTRUCT ccs;
    WSADATA wsadata;
    SOCKERR serr;
    HDC hdc;
    TEXTMETRIC tm;
	HFONT ofont;
    int i, j, isPC_NFS, winShowMode = SW_SHOW;
    char pfn[80], cg[80];
    char md5key[16];

    //  Initialize the sockets library

    serr = WSAStartup(DESIRED_WINSOCK_VERSION, &wsadata);

    if (serr != 0) {
        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(12), serr, SockerrToString(serr));
        return -1;
    }

    if (wsadata.wVersion < MINIMUM_WINSOCK_VERSION) {
        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(13), LOBYTE(wsadata.wVersion),
                HIBYTE(wsadata.wVersion), LOBYTE(MINIMUM_WINSOCK_VERSION),
                HIBYTE(MINIMUM_WINSOCK_VERSION));
        return -1;
    }
    aboutUDPmax = wsadata.iMaxUdpDg;
    isPC_NFS = strcmp(wsadata.szDescription, rstring(IDS_T_SUN_SIGNATURE)) == 0;
    
    /*	If the network can't transmit sound packets as large as we'd like
    	to send, restrict the size to what it can accommodate.  */
    	
    if (aboutUDPmax > 0) {
    	if ((netMaxSamples + (sizeof(soundbuf) - BUFL) + 40) > (UINT) aboutUDPmax) {
    		netMaxSamples = aboutUDPmax - ((sizeof(soundbuf) - BUFL) + 40); 
    	}
    }

    //  Create the command and control sockets

    serr = CreateSocket(&sCommand, SOCK_DGRAM, htonl(INADDR_ANY), htons(NETFONE_COMMAND_PORT));
	if (serr != 0) {
        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(14),
                serr, SockerrToString(serr));
        return -1;
	}
    serr = CreateSocket(&sControl, SOCK_DGRAM, htonl(INADDR_ANY),
    					htons(NETFONE_COMMAND_PORT + 1));
	if (serr != 0) {
        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(14),
                serr, SockerrToString(serr));
        return -1;
	}
   
    //	Load cursors
    
    phoneCursor = LoadCursor(hInst, MAKEINTRESOURCE(IDC_CURSOR_PHONE));
    earCursor = LoadCursor(hInst, MAKEINTRESOURCE(IDC_CURSOR_EAR));
    boltCursor = LoadCursor(hInst, MAKEINTRESOURCE(IDC_CURSOR_BOLT));
    
    //	Register the message for file open/save help button pressed
    
    fileOpenHelpButton = RegisterWindowMessage(HELPMSGSTRING);
    
    //	Initialise GSM encoding and decoding

    gsmh = gsm_create();

	// Initialise LPC encoding and decoding

	if (!lpc_start()) {
        MsgBox(NULL, MB_ICONSTOP | MB_OK, rstring(IDS_T_LWL_ALLOC));
        return -1;
	}
	lpc_init();

	//	Initialise LPC10 decoding

	lpc10init();
		
	//	Initialise CELP decoding
		
#ifndef CELP_USE_CONTEXT
	celp_init(FALSE);
#endif

	//	Initialise simple (2X) compression

	rate_start(&xrate, 8000, 4000);
    
    //	Initialise DES

#ifdef CRYPTO    
    desinit(1);
#endif    
    
    //	Initialise RTP randomised parameters

	sessionKeyGenerate(md5key, TRUE);
	memcpy((char *) &ssrc, md5key, sizeof ssrc);
	memcpy((char *) &timestamp, md5key + sizeof ssrc,
		  sizeof timestamp);
	memcpy((char *) &seq, md5key + sizeof ssrc + sizeof timestamp,
		  sizeof seq);
	memcpy((char *) &rtpdesrand, md5key + sizeof ssrc + sizeof timestamp +
		  sizeof seq, sizeof rtpdesrand);

    //  Create the MDI client window

    ccs.hWindowMenu  = GetSubMenu(GetMenu(hwnd), 3);	// Identify Window menu
    ccs.idFirstChild = IDM_WINDOW_FIRST_CHILD;

    hwndMDIClient = CreateWindow(pszMDIClientClass, NULL,
                                 WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL,
                                 0, 0, 0, 0, hwnd, 0, hInst, (LPSTR) &ccs);

    if (hwndMDIClient == NULL) {
        return -1;
    }

    ShowWindow(hwndMDIClient, winShowMode);
   
    //	Load remembered recent connections
    
	strcpy(pfn, rstring(IDS_PF_PROFILE_FILE));
    strcpy(cg, rstring(IDS_PF_CONNECTIONS));
    rememberedConnections = GetPrivateProfileInt(cg, rstring(IDS_PF_CONNCOUNT),
    	0, pfn);
    if (rememberedConnections > REMEMBER_CONNECTIONS) {
    	rememberedConnections = REMEMBER_CONNECTIONS;
    }
    for (i = j = 0; i < rememberedConnections; i++) {
    	char psn[12], rcr[MAX_HOST];
    	
    	wsprintf(psn, Format(2), i + 1);
    	GetPrivateProfileString(cg, psn, "", rcr,
    		sizeof rcr, pfn);
    	if (strlen(rcr) > 0) {
    		LPSTR rcon = (LPSTR) GlobalAllocPtr(GPTR, strlen(rcr) + 1);
    		
    		if (rcon!= NULL) {
    			strcpy(rcon, rcr);
    			rememberedConnection[j++] = rcon;
    		} 
    	} 
    }
    rememberedConnections = j;
    
#ifdef IP_MAX_MEMBERSHIPS
    
    //	Restore multicast group memberships
    
    strcpy(cg, rstring(IDS_PF_MULTIGROUPS));
    multiMemberships = GetPrivateProfileInt(cg, rstring(IDS_PF_CONNCOUNT),
    	0, pfn);
    if (multiMemberships > IP_MAX_MEMBERSHIPS) {
    	multiMemberships = IP_MAX_MEMBERSHIPS;
    }
    for (i = 0; i < multiMemberships; i++) {
    	char psn[24], rcr[MAX_HOST];
    	
    	wsprintf(psn, Format(3), i + 1);
    	GetPrivateProfileString(cg, psn, "", rcr,
    		sizeof rcr, pfn);
    	multiAddr[i].s_addr = inet_addr(rcr);
    	wsprintf(psn, Format(4), i + 1);
    	GetPrivateProfileString(cg, psn, "", rcr, sizeof rcr, pfn);
    	multiName[i] = NULL;
    	if (strlen(rcr) > 0) {
    		LPSTR rcon = (LPSTR) GlobalAllocPtr(GPTR, strlen(rcr) + 1);
    		
    		if (rcon!= NULL) {
    			strcpy(rcon, rcr);
    			multiName[i] = rcon;
    		} 
    	} 
    }
    multiLoop = GetPrivateProfileInt(cg, rstring(IDS_PF_LOOPBACK), TRUE, pfn);
    multicastJoin(hwnd, TRUE);
#endif    
    
    //	Restore compression modes
    
#define BoolProfile(var, section, itemname, defval) var = GetPrivateProfileInt(section, itemname, defval, pfn)     
    strcpy(cg, rstring(IDS_PF_COMPRESSION));
    BoolProfile(compression, cg, rstring(IDS_PF_COMP_SIMPLE), FALSE);
    BoolProfile(gsmcompress, cg, rstring(IDS_PF_COMP_GSM), TRUE);
    BoolProfile(adpcmcompress, cg, rstring(IDS_PF_COMP_ADPCM), FALSE);
    BoolProfile(lpccompress, cg, rstring(IDS_PF_COMP_LPC), FALSE);
	BoolProfile(lpc10compress, cg, rstring(IDS_PF_COMP_LPC10), FALSE);
	BoolProfile(celpcompress, cg, rstring(IDS_PF_COMP_CELP), FALSE);
    
    //	Restore transmit protocol
    
    strcpy(cg, rstring(IDS_PF_PROTOCOL));
    protocolXmit = protocolSent = GetPrivateProfileInt(cg, rstring(IDS_PF_PROTOCOL_XMIT), PROTOCOL_SPEAKFREE, pfn);
	BoolProfile(protocolAuto, cg, rstring(IDS_PF_PROTOCOL_SEND_RECEIVED), TRUE);
	robust = GetPrivateProfileInt(cg, rstring(IDS_PF_ROBUST), TRUE, pfn);
	
	//	Encryption modes
	
	{
		static char pk_package_name[12];		// Public key package name

		strcpy(cg, rstring(IDS_PF_ENCRYPTION_MODES));
		GetPrivateProfileString(cg, rstring(IDS_PF_PUBLIC_KEY), rstring(IDS_PF_PUBLIC_KEY_PGP), pk_package_name,
    			sizeof pk_package_name, pfn);
    	if (strcmp(pk_package_name, rstring(IDS_PF_PUBLIC_KEY_PGP)) == 0) {
    		pk_package = PGP;
    	} else if (strcmp(pk_package_name, rstring(IDS_PF_PUBLIC_KEY_GPG)) == 0) {
    		pk_package = GPG;
    	} else {
    		pk_package = Unspecified;
    	}
    }

#ifdef MODEM
     
    //	Restore modem configuration and initialise if enabled
    
    strcpy(cg, rstring(IDS_PF_MODEM));
    BoolProfile(modemEnable, cg, rstring(IDS_PF_ENABLE), FALSE);
    GetPrivateProfileString(cg, rstring(IDS_PF_PORT), "COM1", commport,
    		sizeof commport, pfn);
    GetPrivateProfileString(cg, rstring(IDS_PF_BAUDRATE), "19200", baudrate,
    		sizeof baudrate, pfn);
    GetPrivateProfileString(cg, rstring(IDS_PF_MODEM_INIT_STRING), "ATQ0V1E1S0=1",
    		modemInitString, sizeof modemInitString, pfn);
	BoolProfile(modemShowRant, cg, rstring(IDS_PF_SHOW_RANT), TRUE);
#endif	
	
	//	Restore answering machine settings
	
    strcpy(cg, rstring(IDS_PF_ANSWER));
	GetPrivateProfileString(cg, rstring(IDS_PF_FILE_NAME), "", answerFileName,
    		sizeof answerFileName, pfn);
	GetPrivateProfileString(cg, rstring(IDS_PF_OUT_FILE_NAME), "", answerOutFileName,
    		sizeof answerOutFileName, pfn);
   	BoolProfile(answerRecord, cg, rstring(IDS_PF_RECORD_MESSAGES), FALSE);
   	
	//	Restore show your face settings
	
	GetPrivateProfileString(rstring(IDS_PF_FACE), rstring(IDS_PF_FILE_NAME), "", faceFileName,
    		sizeof faceFileName, pfn);
   	BoolProfile(faceShow, rstring(IDS_PF_FACE), rstring(IDS_PF_SHOW_FACES), TRUE);	
	    	
	//	Restore miscellaneous configuration parameters
	
    GetPrivateProfileString(rstring(IDS_PF_RING), rstring(IDS_PF_FILE_NAME), "",
    		ringFileName, sizeof ringFileName, pfn);
	jitterBuf = GetPrivateProfileInt(rstring(IDS_PF_PREFERENCES), rstring(IDS_PF_JITTER), jitterBuf, pfn);
	
	//	Restore window display configuration parameters
//    BoolProfile(lookWho_sTalking, rstring(IDS_PF_PREFERENCES), rstring(IDS_PF_LOOKWHO), FALSE);
#define WindowPref(var, prefName)	BoolProfile(var, rstring(IDS_PF_DISPLAY_MODES), rstring(IDS_PF_OPT_ ## prefName), FALSE)
	WindowPref(lookWho_sTalking, LOOK_WHO);
	WindowPref(openOnAnswerMessage, OPEN_ON_ANSWER_MESSAGE);
	WindowPref(lurkInTray, USE_TRAY_ICON);
	WindowPref(hideWhenMinimised, HIDE_WHEN_MINIMISED);
	WindowPref(startMinimised, START_MINIMISED);
#undef WindowPref

	//	Restore Look Who's Listening parameters
	
	GetPrivateProfileString(rstring(IDS_PF_LWL_TELL), rstring(IDS_PF_SERVER), "",
    		lwl_s_server, sizeof lwl_s_server, pfn);
	GetPrivateProfileString(rstring(IDS_PF_LWL_TELL), rstring(IDS_PF_EMAIL), "",
    		lwl_s_email, sizeof lwl_s_email, pfn);
	GetPrivateProfileString(rstring(IDS_PF_LWL_TELL), rstring(IDS_PF_FULLNAME), "",
    		lwl_s_fullname, sizeof lwl_s_fullname, pfn);
	GetPrivateProfileString(rstring(IDS_PF_LWL_TELL), rstring(IDS_PF_PHONE), "",
    		lwl_s_phone, sizeof lwl_s_phone, pfn);
	GetPrivateProfileString(rstring(IDS_PF_LWL_TELL), rstring(IDS_PF_LOCATION), "",
    		lwl_s_location, sizeof lwl_s_location, pfn);
    BoolProfile(lwl_s_publish, rstring(IDS_PF_LWL_TELL), rstring(IDS_PF_PUBLISH), FALSE);
    BoolProfile(lwl_s_exact, rstring(IDS_PF_LWL_TELL), rstring(IDS_PF_EXACT), FALSE);

	GetPrivateProfileString(rstring(IDS_PF_LWL_ASK), rstring(IDS_PF_SERVER), "",
    		lwl_a_server, sizeof lwl_a_server, pfn);
    BoolProfile(lwl_a_exact, rstring(IDS_PF_LWL_ASK), rstring(IDS_PF_EXACT), FALSE);
    		
    //	Restore bug workaround parameters
    
    strcpy(cg, rstring(IDS_PF_WORKAROUNDS));
    BoolProfile(alwaysBindSocket, cg, rstring(IDS_PF_ALWAYS_BIND), FALSE);	
    BoolProfile(waNetNoConnect, cg, rstring(IDS_PF_NET_NO_CONNECT), FALSE);	
    BoolProfile(waNetUseSend, cg, rstring(IDS_PF_NET_USE_SEND), FALSE);	
    BoolProfile(waNetMultiTTLisChar, cg, rstring(IDS_PF_MULTICAST_CHAR_ARG), FALSE);	
    BoolProfile(waNetNoOutOverflow, cg, rstring(IDS_PF_NO_OUT_OVERFLOW), FALSE);	
    BoolProfile(waNetNoMsgLoopIns, cg, rstring(IDS_PF_NO_MSG_LOOP), FALSE);	
    waNetSynchronousGetHostname = GetPrivateProfileInt(cg, rstring(IDS_PF_SYNC_HOSTNAME), 2, pfn);
    
    BoolProfile(waProtNoHeartbeat, cg, rstring(IDS_PF_WA_NO_HEARTBEAT), FALSE);    
    BoolProfile(waProtUseLargerRTCPackets, cg, rstring(IDS_PF_WA_RTP_LARGER_PKTS), FALSE);    
    BoolProfile(waProtNoVAT, cg, rstring(IDS_PF_WA_NO_VAT), FALSE);    
    BoolProfile(waProtNoRTP, cg, rstring(IDS_PF_WA_NO_RTP), FALSE);    
    BoolProfile(waProtNoRTCPCrypt, cg, rstring(IDS_PF_WA_NO_RTCP_CRYPT), FALSE);    
    	
    BoolProfile(waAudioHalf, cg, rstring(IDS_PF_AUDIO_HALF_DUPLEX), FALSE);	
    BoolProfile(waAudio11025, cg, rstring(IDS_PF_AUDIO_SAMPLE_11025), FALSE);
    BoolProfile(waAudioRingMaxVolume, cg, rstring(IDS_PF_RING_VOLUME), FALSE);
    BoolProfile(waAudioNoOutputRateAdjustment, cg, rstring(IDS_PF_NO_OUTPUT_RATE_ADJUSTMENT), FALSE);
    waAudioInDevice = GetPrivateProfileInt(cg, rstring(IDS_PF_AUDIO_IN_DEVICE), WAVE_MAPPER, pfn);
    waAudioOutDevice = GetPrivateProfileInt(cg, rstring(IDS_PF_AUDIO_OUT_DEVICE), WAVE_MAPPER, pfn);
	waNetSynchronousGetHostnameAction = (waNetSynchronousGetHostname == 2) ? isPC_NFS :
											waNetSynchronousGetHostname;

	// Load the RLE and VOX parameters
	
	load_vox_params();

	//	Load audio monitor parameters
	
	strcpy(cg, rstring(IDS_PF_MONITOR));	
	BoolProfile(spectrumBarGraph, cg, rstring(IDS_PF_MON_BARGRAPH), TRUE);
	BoolProfile(spectrumVoicePrint, cg, rstring(IDS_PF_MON_VOICEPRINT), FALSE);
	BoolProfile(spectrumTransmitOnly, cg, rstring(IDS_PF_MON_TRANSMIT), FALSE);
	BoolProfile(spectrumReceiveOnly, cg, rstring(IDS_PF_MON_RECEIVE), FALSE);
	BoolProfile(spectrumMaxEnergy, cg, rstring(IDS_PF_MON_ENV_MAX), FALSE);

#undef BoolProfile
	useSendNotSendto = waNetUseSend; 

    //	Check out the audio hardware
    
    halfDuplex = isHalfDuplex(hwnd);

    //  Create the timeout timer

    if (SetTimer(hwnd, FRAME_TIMER_ID, 1000, NULL) == 0) {
        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(15));
        return -1;
    }
	    	
	//	Calculate packet size based on selected modes
	
    currentInputLength = inputBufferLength();
    currentInputSamples = inputSampleCount();	

    //  Get textmetric data

    hdc = GetDC(hwnd);
	ofont = SelectObject(hdc, GetStockObject(ANSI_VAR_FONT));
    GetTextMetrics(hdc, &tm);
	SelectObject(hdc, ofont);
    ReleaseDC(hwnd, hdc);

    tmAveCharWidth = (INT) tm.tmAveCharWidth;
    tmHeight = (INT) tm.tmHeight;
    
    DragAcceptFiles(hwnd, TRUE);
    
#ifdef TRAY
	//	Create notification icon in system tray
	
	if (lurkInTray) {
		trayIconWindow = createTrayIcon();
		updateTrayToolTipText();
	}
#endif
    
    /*	If an answering machine message file was named, open
    	it.  This doesn't imply we're recording messages; that's
    	controlled by answerRecord.  */
    
    if (answerFileName[0] != 0) {
		answerOpen();
	}
	
	//	Open the face image file, if any
	
	V openFaceFile(hwnd);

	/*	If we're publishing our information with a Look Who's Listening
		server, establish contact with it.  */
		
	V lwl_reconnect(hwnd);

	//	Finally, open the input sockets for business.
	
    if (WSAAsyncSelect(sCommand, hwnd, WM_SOCKET_SELECT, FD_READ) != 0) {
        serr = WSAGetLastError();
    }
    if (serr != 0) {
        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(14),
                serr, SockerrToString(serr));
    }	
    if (WSAAsyncSelect(sControl, hwnd, WM_SOCKET_CONTROL, FD_READ) != 0) {
        serr = WSAGetLastError();
    }
    if (serr != 0) {
        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(14),
                serr, SockerrToString(serr));
    }

	createSuccessful = TRUE;	
    return TRUE;
}

/*  onCommand  --  Dispatch WM_COMMAND messages.  Some are processed
				   at the frame level, some sent to the MDI client,
				   and others forwarded to the active MDI connection
				   window. */

static VOID onCommand(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify)
{
	if (id > IDM_CUSTOM && id <= (IDM_CUSTOM + rememberedConnections)) {
		newConnection(hwnd, rememberedConnection[(id - IDM_CUSTOM) - 1], NULL);
		return;
	}

    switch (id) {
    
	    case IDM_CONNECTION_NEW:
	        newConnection(hwnd, NULL, NULL);
	        break;
	    	
	    case IDM_CONN_OPEN:
	     	{
	     		OPENFILENAME ofn;
	     		char cfName[MAX_PATH];
	     		
	            memset(&ofn, 0, sizeof(ofn));
				ofn.lStructSize = sizeof(OPENFILENAME);
				ofn.hwndOwner = hwnd;
				ofn.lpstrFilter = rfilter(IDS_T_CONNECTION_FILTER);
				ofn.lpstrCustomFilter = NULL;
				cfName[0] = 0;
				ofn.lpstrFile = (LPSTR) cfName;
				ofn.nMaxFile = sizeof cfName;
				ofn.lpstrInitialDir = NULL;
				ofn.lpstrTitle = rstring(IDS_T_CONNECTION_OPEN_TITLE);
				ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_SHOWHELP;
				fileHelpKey = rstring(IDS_HELP_OPEN_CONNECTION);
				if (GetOpenFileName((LPOPENFILENAME) &ofn) == 0) {
					break;
				}
				newConnection(hwnd, cfName, NULL);
	     	}
	    	break;
	    	
	    case IDM_CONN_SAVE_AS:
	     	{
	     		OPENFILENAME ofn;
	     		HWND cWnd = getActiveConnection();
	     		
	     		if (cWnd != NULL) {
		     		LPCLIENT_DATA d = CLIENTPTR(cWnd);
	     		
					d->timeout = -1;				// Make connection immortal
		            memset(&ofn, 0, sizeof(ofn));
					ofn.lStructSize = sizeof(OPENFILENAME);
					ofn.hwndOwner = hwnd;
					ofn.lpstrDefExt = "SFX";
					ofn.lpstrFilter = rfilter(IDS_T_CONNECTION_FILTER);
					ofn.lpstrCustomFilter = NULL;
					d->szFile[0] = 0;
					ofn.lpstrFile = (LPSTR) d->connectionFileName;
					ofn.nMaxFile = sizeof d->connectionFileName;
					ofn.lpstrInitialDir = NULL;
					ofn.lpstrTitle = rstring(IDS_T_CONNECTION_SAVE_TITLE);
					ofn.Flags = OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT |
								OFN_HIDEREADONLY | OFN_SHOWHELP;
					fileHelpKey = rstring(IDS_HELP_SAVE_CONNECTION);
					if (GetSaveFileName((LPOPENFILENAME) &ofn) == 0) {
						break;
					}
				}
	     	}
	     	//	Wheee!!!  Note fall-through
	     	
	     	case IDM_CONN_SAVE:
	     	{
	     		HWND cWnd = getActiveConnection();
	     		
	     		if (cWnd != NULL) {
		     		LPCLIENT_DATA d = CLIENTPTR(cWnd);
		     		char mcs[10], pno[10];
		     		
		     		wsprintf(mcs, Format(5), d->multicast_scope);
		     		wsprintf(pno, Format(5), d->port);
					d->timeout = -1;				// Make connection immortal
		     		if (WritePrivateProfileString(rstring(IDS_PF_HOST), rstring(IDS_PF_HOST_NAME),
		     				d->szHost, d->connectionFileName) == 0 ||
		     			WritePrivateProfileString(rstring(IDS_PF_HOST), rstring(IDS_PF_NETADDR),
		     				inet_ntoa(d->inetSock.sin_addr), d->connectionFileName) == 0 ||
		     			WritePrivateProfileString(rstring(IDS_PF_HOST), rstring(IDS_PF_PORT_NUMBER),
		     				pno, d->connectionFileName) == 0 ||
		     			WritePrivateProfileString(rstring(IDS_PF_DEBUG), rstring(IDS_PF_DEBUGGING),
		     				d->debugging ? kS1 : kS0, d->connectionFileName) == 0 ||
		     			WritePrivateProfileString(rstring(IDS_PF_DEBUG), rstring(IDS_PF_LOOPBACK),
		     				d->loopback ? kS1 : kS0, d->connectionFileName) == 0 ||
		     			WritePrivateProfileString(rstring(IDS_PF_MULTICAST), rstring(IDS_PF_SCOPE),
		     				mcs, d->connectionFileName) == 0 ||
		     			WritePrivateProfileString(rstring(IDS_PF_ENCRYPTION), rstring(IDS_PF_SAVE_KEYS),
		     				d->saveKeys ? kS1 : kS0, d->connectionFileName) == 0 ||
		     			WritePrivateProfileString(rstring(IDS_PF_ENCRYPTION), rstring(IDS_PF_PGP_USER_NAMES),
		     				d->opgpUserList, d->connectionFileName) == 0 ||
		     			(!d->saveKeys ? 0 : (
			     			WritePrivateProfileString(rstring(IDS_PF_ENCRYPTION), rstring(IDS_PF_DES_KEY),
			     				d->desKeyString, d->connectionFileName) == 0 ||
			     			WritePrivateProfileString(rstring(IDS_PF_ENCRYPTION), rstring(IDS_PF_IDEA_KEY),
			     				d->ideaKeyString, d->connectionFileName) == 0 ||
			     			WritePrivateProfileString(rstring(IDS_PF_ENCRYPTION), rstring(IDS_PF_BLOWFISH_KEY),
			     				d->blowfishKeyString, d->connectionFileName) == 0 ||
			     			WritePrivateProfileString(rstring(IDS_PF_ENCRYPTION), rstring(IDS_PF_AES_KEY),
			     				d->aesKeyString, d->connectionFileName) == 0 ||
		     				WritePrivateProfileString(rstring(IDS_PF_ENCRYPTION), rstring(IDS_PF_AES_KEY_HEX),
		     					d->aesKeyHex ? kS1 : kS0, d->connectionFileName) == 0 ||
			     			WritePrivateProfileString(rstring(IDS_PF_ENCRYPTION), rstring(IDS_PF_KEY_FILE),
			     				d->otpFileName, d->connectionFileName) == 0))) {
				        	MessageBox(hwnd, rstring(IDS_T_CONN_SAVE_ERR), NULL,
						   		MB_OK | MB_ICONEXCLAMATION);
		     		} else {
	    				rememberNewConnection(d->connectionFileName);
		     		}
		     	}
		    }
	    	break;	    	
	        
	    case IDM_CONN_PROPERTIES:
			{
				HWND cWnd = getActiveConnection();
				
				if (cWnd != NULL) {
					CLIENTPTR(cWnd)->timeout = -1;		// Make connection immortal
	    			connectionProperties(cWnd, CLIENTPTR(cWnd));
                	if (!(CLIENTPTR(cWnd)->face_shown)) {
			    		InvalidateRect(cWnd, NULL, FALSE);
			    		PostMessage(cWnd, WM_PAINT, 0, 0);
			    	}
				}
			}
	    	break;

		case IDM_CONN_RING:
	     	{
	     		HWND cWnd = getActiveConnection();
	     		
	     		if (cWnd != NULL) {
		     		CLIENTPTR(cWnd)->ring = TRUE;
		     	}
		    }
			//	Wheee!!!  Fall through into Send Sound File
	    	
	    case IDM_SEND_SOUND_FILE:
			{
	     		OPENFILENAME ofn;
	     		HWND cWnd = getActiveConnection();
	     		
	     		if (cWnd != NULL) {
		     		LPCLIENT_DATA d = CLIENTPTR(cWnd);
		     		int originalTimeout = d->timeout;
		     		int ringer = d->ring;
		     		
		     		/* Make the window "temporarily immortal" so it doesn't
		     		   disappear due to timeout while the send file dialogue
		     		   is up. */
		     		d->ring = FALSE;
		            d->timeout = -1;
		            if (d->hFile == HFILE_ERROR && d->mmioHandle == NULL) {
		            	int s = FALSE;
		            	
			            memset(&ofn, 0, sizeof(ofn));
						ofn.lStructSize = sizeof(OPENFILENAME);
						ofn.hwndOwner = hwnd;
						ofn.lpstrFilter = rfilter(IDS_T_SOUND_FILE_FILTER);
						ofn.lpstrCustomFilter = NULL;
						d->szFile[0] = 0;
						ofn.lpstrFile = (LPSTR) d->szFile;
						ofn.nMaxFile = sizeof(d->szFile);
						ofn.lpstrInitialDir = NULL;
						ofn.lpstrTitle = ringer ? rstring(IDS_T_RING_TITLE) :
												  rstring(IDS_T_SEND_SOUND_TITLE);
						ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_SHOWHELP;
						fileHelpKey = ringer ? rstring(IDS_HELP_RING_FILE) :
										       rstring(IDS_HELP_SEND_SOUND_FILE);
						if (ringer && ringFileName[0] != 0) {
							HFILE hFile;

							hFile = _lopen(ringFileName, OF_READ | OF_SHARE_DENY_WRITE);
							if (hFile != HFILE_ERROR) {
								_lclose(hFile);
								strcpy(d->szFile, ringFileName);
								s = TRUE;
							}
						}
						if (!s) {
							s = GetOpenFileName((LPOPENFILENAME) &ofn);
							if (ringer) {
								strcpy(ringFileName, d->szFile);
							}
						} 
						if (s) {
							startSoundFile(cWnd, d->szFile);
						} else {
							/*	User canceled send file dialogue.  Restore
								timeout to the original value.  */
							d->timeout = originalTimeout;
						}
					} else {
						d->quitSoundFile = TRUE;
					}
				}
	     	}
	    	break;
         	
		case IDM_CO_REPONDEUR:
			answerDialogue(hwnd);
			break;
			
		case IDM_CO_RECORD:
			answerRecord = !answerRecord;
			if (hDlgAnswer != NULL) {
				CheckDlgButton(hDlgAnswer, IDC_RP_RECORD, answerEnabled() && answerRecord);
			}
			break;

		case IDM_CONN_BROADCAST:
			if (bConferencing || bEchoPackets) {
				MessageBox(hwnd, rstring(IDS_T_NO_BROADCAST), pszAppName,
								 MB_OK | MB_ICONEXCLAMATION);
				break;	// Don't allow broadcast while conference or echo set
			}

			broadcasting = !broadcasting;
			if (broadcasting) {
			    HWND hwnd = GetWindow(hwndMDIClient, GW_CHILD);
			    DWORD bstart = GetTickCount();

				//	Clear wants input in any sending windows
			
			    while (hwnd != NULL) {
			    	if ((WNDPROC) GetWindowLong(hwnd, GWL_WNDPROC) == ((WNDPROC) connectWndProc)) {
				        LPCLIENT_DATA pClientData = CLIENTPTR(hwnd);
				
				        if (pClientData != NULL) {
				            pClientData->wantsInput = FALSE;
				            pClientData->broadcastBeginTime = bstart;
				            pClientData->broadcastEnd = FALSE;
				        }
				    }
			        hwnd = GetWindow(hwnd, GW_HWNDNEXT);
			    }
			    listeners = 0;
				if (!inputActive) {
					V startWaveInput(hwnd);
					inputPaused = FALSE;
				}
			} else {
				terminateWaveInput();
				inputPaused = FALSE;
				SetCursor(LoadCursor(NULL, IDC_ARROW));
			}
			SetWindowText(hwndMDIFrame, rstring(broadcasting ?
				IDS_T_APP_TITLE_BROADCAST : IDS_T_APP_TITLE_NORM));
			break;

		case IDM_CONN_CONFERENCE:
			if (broadcasting || bEchoPackets) {
				MessageBox(hwnd, rstring(IDS_T_NO_CONFERENCE), pszAppName,
								 MB_OK | MB_ICONEXCLAMATION);
				break;	// Don't allow conference while echo of broadcast set
			}
			bConferencing = !bConferencing;

#ifdef OBSOLETE			
			if (bConferencing) {
				HWND hwnd = GetWindow(hwndMDIClient, GW_CHILD);
				DWORD bstart = GetTickCount();

				//	Clear wants input in any sending windows
			
				while (hwnd != NULL) {
			    	if ((WNDPROC) GetWindowLong(hwnd, GWL_WNDPROC) == ((WNDPROC) connectWndProc)) {
						LPCLIENT_DATA pClientData = CLIENTPTR(hwnd);
				
						if (pClientData != NULL) {
							pClientData->wantsInput = FALSE;
							pClientData->broadcastBeginTime = bstart;
							pClientData->broadcastEnd = FALSE;
						}
					}
					hwnd = GetWindow(hwnd, GW_HWNDNEXT);
				}
				listeners = 0;
				if (!inputActive) {
					V startWaveInput(hwnd);
					inputPaused = FALSE;
				}
			} else {
				terminateWaveInput();
				inputPaused = FALSE;
				SetCursor(LoadCursor(NULL, IDC_ARROW));
			}
#endif
			SetWindowText(hwndMDIFrame, rstring(bConferencing ?
				IDS_T_APP_TITLE_CONFERENCE : IDS_T_APP_TITLE_NORM));
			break;

		case IDM_CONN_ECHO:
			if (broadcasting || bConferencing) {
				MessageBox(hwnd, rstring(IDS_T_NO_ECHO), pszAppName,
							MB_OK | MB_ICONEXCLAMATION);
				break;	// Don't allow echo while conference or broadcast set
			}

			bEchoPackets = !bEchoPackets;

			SetWindowText(hwndMDIFrame, rstring(bEchoPackets ?
				IDS_T_APP_TITLE_ECHO : IDS_T_APP_TITLE_NORM));
			break;

	    case IDM_RING_FILE_NAME:
			{
	     		OPENFILENAME ofn;
	     		char fName[MAX_PATH];
	     		
	            memset(&ofn, 0, sizeof(ofn));
				ofn.lStructSize = sizeof(OPENFILENAME);
				ofn.hwndOwner = hwnd;
				ofn.lpstrFilter = rfilter(IDS_T_SOUND_FILE_FILTER);
				ofn.lpstrCustomFilter = NULL;
				strcpy(fName, ringFileName);
				ofn.lpstrFile = (LPSTR) fName;
				ofn.nMaxFile = sizeof(fName);
				ofn.lpstrInitialDir = NULL;
				ofn.lpstrTitle = rstring(IDS_T_RING_TITLE);
				ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_SHOWHELP;
				fileHelpKey = rstring(IDS_HELP_RING_FILE);
				if (GetOpenFileName((LPOPENFILENAME) &ofn)) {
					strcpy(ringFileName, fName);
				}
	     	}
	    	break;

#ifdef IP_MAX_MEMBERSHIPS	    	
	    case IDM_CONN_MULTICAST:
	    	multicastGroupsDialogue(hwnd);
	    	break;
#endif	    	

		case IDM_DISCONNECT:
			{
				HWND hActive;
				hActive = (HWND) SendMessage(hwndMDIClient, WM_MDIGETACTIVE, 0, 0L);
				if (hActive != NULL) {
					PostMessage(hActive, WM_CLOSE, 0, 0L);
				}
			}
			break;
			
		case IDM_OPT_8BIT:
			audioUse8Bit = !audioUse8Bit;
			if (!audioUse8Bit && !audioIs8Bit) {
				sampleAlignment = 2;
				bitsPerSample = 16;
			} else {
				sampleAlignment = 1;
				bitsPerSample = 8;
			}
			bytesPerSecond = samplesPerSecond * sampleAlignment;
	    	propUpdateAudio();
			break;

		case IDM_OPT_BREAK:
			breakinput = !breakinput;
			break;
			
		//	Window display options menu

#define WindowPref(var, prefName)	case IDM_OPT_ ## prefName: var = !var; break
				WindowPref(lookWho_sTalking, LOOK_WHO);
				WindowPref(openOnAnswerMessage, OPEN_ON_ANSWER_MESSAGE);
				WindowPref(hideWhenMinimised, HIDE_WHEN_MINIMISED);
				WindowPref(startMinimised, START_MINIMISED);
#undef WindowPref
			
		case IDM_OPT_USE_TRAY_ICON:		/* A tad more complicated here since we want to
										   immediately display or hide the tray icon. */
			lurkInTray = !lurkInTray;
			if (lurkInTray) {
				if (trayIconWindow == NULL) {
					trayIconWindow = createTrayIcon();
					updateTrayToolTipText();
				}
			} else {
				if (trayIconWindow != NULL) {
					DestroyWindow(trayIconWindow);
					trayIconWindow = NULL;
				}
			}
			break;
			
		//	Jitter compensation settings
			
#define Jitter(time) case IDM_OPT_JITTER_##time: jitterBuf = time; break
        Jitter(0);
		Jitter(100); 
		Jitter(250); 
		Jitter(500); 
		Jitter(750); 
		Jitter(1000); 
		Jitter(2000); 
		Jitter(3000);
#undef Jitter		 
			
		//	Network workarounds	
					
		case IDM_WORKA_BIND:
			alwaysBindSocket = !alwaysBindSocket;
			break;
			
		case IDM_WORKA_NOCONNECT:
			waNetNoConnect = !waNetNoConnect;
			if (waNetNoConnect) {
				waNetUseSend = FALSE;
			}
			break;
			
		case IDM_WORKA_USE_SEND:
			waNetUseSend = !waNetUseSend;
			useSendNotSendto = waNetUseSend; 
			if (waNetUseSend) {
				waNetNoConnect = FALSE;
			}
			if (hDlgPropeller != NULL) {
				SetDlgItemText(hDlgPropeller, IDC_PH_SENDTO,
					useSendNotSendto ? rstring(IDS_T_SEND) : rstring(IDS_T_SENDTO));
			}
			break;
			
		case IDM_WORKA_TTLCHAR:
			waNetMultiTTLisChar = !waNetMultiTTLisChar;
			break;
			
		case IDM_WORKA_OUT_OVERFLOW:
			waNetNoOutOverflow = !waNetNoOutOverflow;
			break;
			
		case IDM_WORKA_MSG_LOOP:
			waNetNoMsgLoopIns = !waNetNoMsgLoopIns;
			break;
			
		case IDM_WORKA_SYNC_HOSTNAME:
			waNetSynchronousGetHostnameAction = waNetSynchronousGetHostname =
					!waNetSynchronousGetHostnameAction;
			break;
			
		//	Protocol workarounds
			
		case IDM_WORKA_NOHB:
			waProtNoHeartbeat = !waProtNoHeartbeat;
			break;
			
		case IDM_WORKA_BIGRTP:
			waProtUseLargerRTCPackets = !waProtUseLargerRTCPackets;
			currentInputLength = inputBufferLength();
	    	currentInputSamples = inputSampleCount();
			break;
			
		case IDM_WORKA_NOVAT:
			waProtNoVAT = !waProtNoVAT;
			break;
			
		case IDM_WORKA_NORTP:
			waProtNoRTP = !waProtNoRTP;
			break;
			
		case IDM_WORKA_NORTCP_CRYPT:
			waProtNoRTCPCrypt = !waProtNoRTCPCrypt;
			break;
			

		//	Audio workarounds			
			
		case IDM_WORKA_HALF_DUPLEX:
			waAudioHalf = !waAudioHalf;
        	MessageBox(hwnd, rstring(IDS_T_EXIT_AND_RESTART), pszAppName,
		   		MB_OK | MB_ICONINFORMATION);			
			break;
			
		case IDM_WORKA_SAMPLE_11025:
			waAudio11025 = !waAudio11025;
        	MessageBox(hwnd, rstring(IDS_T_EXIT_AND_RESTART), pszAppName,
		   		MB_OK | MB_ICONINFORMATION);			
			break;
			
		case IDM_WORKA_RING_VOLUME:
			waAudioRingMaxVolume = !waAudioRingMaxVolume;
			break;

		case IDM_WORKA_DISABLE_AORA:
			waAudioNoOutputRateAdjustment = !waAudioNoOutputRateAdjustment;
			break;
			
		case IDM_VOX_FAST:
		case IDM_VOX_MEDIUM:
		case IDM_VOX_SLOW:
		case IDM_VOX_NONE:
			if (voxmode != id) {
				voxmode = id;
            }
			break;

		case IDM_VOX_MONITOR:
			voxMonitorDialog(hwnd);
			break;

		case IDM_COMP_2X:
			compression = !compression;
			currentInputLength = inputBufferLength();
	    	currentInputSamples = inputSampleCount();
	    	propUpdateAudio();
			break;
			
		case IDM_COMP_ADPCM:
			adpcmcompress = !adpcmcompress;
			if (adpcmcompress) {
				gsmcompress = FALSE;
				lpccompress = FALSE;
				lpc10compress = FALSE;
				celpcompress = FALSE;
				voxcompress = FALSE;
			}
			currentInputLength = inputBufferLength();
	    	currentInputSamples = inputSampleCount();
	    	propUpdateAudio();
			break;
			
		case IDM_COMP_LPC:
			lpccompress = !lpccompress;
			if (lpccompress) {
				gsmcompress = FALSE;
				adpcmcompress = FALSE;
				lpc10compress = FALSE;
				celpcompress = FALSE;
				voxcompress = FALSE;
			}
			currentInputLength = inputBufferLength();
	    	currentInputSamples = inputSampleCount();
	    	propUpdateAudio();
			break;
			
		case IDM_COMP_LPC10:
			lpc10compress = !lpc10compress;
			if (lpc10compress) {
				gsmcompress = FALSE;
				adpcmcompress = FALSE;
				lpccompress = FALSE;
				celpcompress = FALSE;
				voxcompress = FALSE;
			}
			currentInputLength = inputBufferLength();
	    	currentInputSamples = inputSampleCount();
	    	propUpdateAudio();
			break;
			
		case IDM_COMP_CELP:
			celpcompress = !celpcompress;
			if (celpcompress) {
				gsmcompress = FALSE;
				adpcmcompress = FALSE;
				lpccompress = FALSE;
				lpc10compress = FALSE;
				voxcompress = FALSE;
			}
			currentInputLength = inputBufferLength();
	    	currentInputSamples = inputSampleCount();
	    	propUpdateAudio();
			break;

		case IDM_PROT_ROBUST1:
		case IDM_PROT_ROBUST2:
		case IDM_PROT_ROBUST3:
		case IDM_PROT_ROBUST4:
		case IDM_PROT_ROBUST5:
		case IDM_PROT_ROBUST6:
		case IDM_PROT_ROBUST7:
		case IDM_PROT_ROBUST8:
			robust = 1 + (id - IDM_PROT_ROBUST1);
			break;
			
		case IDM_COMP_GSM:
			gsmcompress = !gsmcompress;
			if (gsmcompress) {
				adpcmcompress = FALSE;
				lpccompress = FALSE;
				lpc10compress = FALSE;
				celpcompress = FALSE;
				voxcompress = FALSE;
			}
			currentInputLength = inputBufferLength();
	    	currentInputSamples = inputSampleCount();
	    	propUpdateAudio();
			break;
			
		case IDM_PROT_SPEAKFREE:
			protocolXmit = PROTOCOL_SPEAKFREE;
newProtocol:
			protocolSent = protocolXmit;			
			
			/*	Force all connections to quickly send a session ID in
				the new protocol. */
				
			{
			    HWND hwnd;
			
			    hwnd = GetWindow(hwndMDIClient, GW_CHILD);
			
			    while (hwnd != NULL) {
			    	if ((WNDPROC) GetWindowLong(hwnd, GWL_WNDPROC) == ((WNDPROC) connectWndProc)) {
				        LPCLIENT_DATA pClientData = CLIENTPTR(hwnd);
				
				        if (pClientData != NULL) {
				        	pClientData->sendSDEStimer = 0;
				        }
				    }
			        hwnd = GetWindow(hwnd, GW_HWNDNEXT);
			    }
			}
			
			currentInputLength = inputBufferLength();
	    	currentInputSamples = inputSampleCount();
			break;
			
		case IDM_PROT_RTP:
			protocolXmit = PROTOCOL_RTP;
nonSpeakFreeProt:
			//	Clear any modes incompatible with RTP and VAT protocols
			compression = FALSE;		// Simple compression not available
			if (lpc10compress) {
				lpc10compress = FALSE;	// Change LPC-10 into LPC compression
				lpccompress = TRUE;
			}
			if (celpcompress) {
				celpcompress = FALSE;	// Turn CELP compression into GSM
				gsmcompress = TRUE;
			}
			goto newProtocol;			
			
		case IDM_PROT_VAT:
			protocolXmit = PROTOCOL_VAT;
			goto nonSpeakFreeProt;
			
		case IDM_PUBLICKEYPACKAGE_PGP:
			pk_package = PGP;
			break;
			
		case IDM_PUBLICKEYPACKAGE_GPG:
			pk_package = GPG;
			break;

#ifdef MODEM			
		case IDM_OPT_MODEM:
			modemSetupDialogue(hwnd);
			break;
#endif			
         
		case IDM_TEXT_CHAT:
			chatDialogue(hwnd);
			break;
			
		case IDM_OPT_FACE:
			faceDialogue(hwnd);
			break;

	    case IDM_CRYPTO_GENKEY:
	    	genKeyDialogue(hwnd);
	    	break;
	
	    case IDM_CONNECTION_EXIT:
	        PostMessage(hwnd, WM_CLOSE, 0, 0);
	        break;
	        
	    case IDM_DIR_SEARCH:
	    	lwl_t_diactive = TRUE;
	    	lwl_ask(hwnd);
	    	lwl_t_diactive = FALSE;
	    	break;
	        
	    case IDM_DIR_LISTING:
	    	lwl_t_diactive = TRUE;
	    	lwl_tell_settings(hwnd);
	    	lwl_t_diactive = FALSE;
	    	break;
	
	    case IDM_WINDOW_CASCADE:
	        FORWARD_WM_MDICASCADE(hwndMDIClient, 0, SendMessage);
	        break;
	
	    case IDM_WINDOW_TILE_VERTICALLY:
	        FORWARD_WM_MDITILE(hwndMDIClient, MDITILE_VERTICAL, SendMessage);
	        break;
	
	    case IDM_WINDOW_TILE_HORIZONTALLY :
	        FORWARD_WM_MDITILE(hwndMDIClient, MDITILE_HORIZONTAL, SendMessage);
	        break;
	
	    case IDM_WINDOW_ARRANGE_ICONS:
	        FORWARD_WM_MDIICONARRANGE(hwndMDIClient, SendMessage);
	        break;

         case IDM_HELP_CONT:
         	displayHelpTopic(IDS_HELP_CONTENTS);
         	break;
         
         case IDM_HELP_LOOPBACK:
			newConnection(hwnd, NULL, rstring(IDS_T_LOCALHOST));
         	break;
         	
         case IDM_HELP_FAQ:
         	displayHelpTopic(IDS_T_FAQ);
         	break;

         case IDM_HELP_SEARCH:
#ifdef HTML_HELP
			{
				/* Naturally, there's no documentation as to how this structure
				   should be initialised just to pop up a query box with a blank
				   initial query, but the following seems to work. */
				static HH_FTS_QUERY q = {
					sizeof(HH_FTS_QUERY),
					FALSE,
					"",
					1,
					FALSE,
					FALSE,
					FALSE,
					""					
				};
				if (!holped) {
					//	Initialise the HTML Help subsystem
					HtmlHelp(NULL, NULL, HH_INITIALIZE, (DWORD) &HtmlHelpContext);
				}
				HtmlHelp(hwndMDIFrame, rstring(IDS_HELPFILE), HH_DISPLAY_SEARCH, (DWORD)&q);
			}
#else
         	WinHelp(hwnd, rstring(IDS_HELPFILE), HELP_PARTIALKEY, ((DWORD) ((LPSTR) "")));
#endif
         	holped = TRUE;
         	break;

         case IDM_HELP_QUICKSTART:
         	displayHelpTopic(IDS_HELP_QUICKSTART);
         	break;

         case IDM_HELP_MAILLIST:
         	displayHelpTopic(IDS_T_MAILLIST);
         	break;

         case IDM_HELP_ECHO:
         	displayHelpTopic(IDS_T_ECHO);
         	break;
         
		case IDM_PROPELLER_HEAD:
			propellerHeadDialogue(hwnd);
			break;
         
		case IDM_SPECTRUM:
			spectralDialogue(hwnd);
			break;
         
		case IDM_HELP_CONSOLE:
			consoleDialogue(hwnd);
			break;
	
	    case IDM_HELP_ABOUT:
	        aboutDialogue(hwnd);
	        break;
	
	    case IDM_HELP_BENCHMARK:
	        benchDialogue(hwnd);
	        break;

#ifdef MODEM	        
	    case IDM_MODEM_RANT:
	    	modemRant(hwnd);
	    	break;
#endif	    	
	
	    default:
	        FORWARD_WM_COMMAND(hwnd, id, hwndCtl,
	        				   codeNotify, Frame_MDIMessageForwarder);
	        break;
    }
}

//	OBTAINOUTPUT  --  Try to obtain control of wave audio output

int obtainOutput(HWND hwnd)
{
	    
    //	Grab the audio device if we don't already have it
	
	outputTimeout = 0;					// Reset release output timeout    
    if (!outputActive) {
	    PCMWAVEFORMAT wfi;    
    	MMRESULT woo;
    	
		/* If we already know the port is half duplex, just ditch the
		   packet right away rather than walking into the door again. */
	    	
		if (halfDuplex && inputActive && !breakinput) {
			propeller(IDC_PH_INPUT_LOST, ++inputPacketsLost);
			return FALSE;
		} else {
			if (halfDuplex && inputActive && breakinput) {
				terminateWaveInput();
				/* terminateWaveInput() does not guarantee input is
				   actually closed when it returns.  It has to wait for
				   all kinds of debris to rattle through the message loop
				   before it's actually done.  So, if that's the case, deep six
				   this packet. */
				inputPaused = TRUE;
				if (inputActive) {
					propeller(IDC_PH_INPUT_LOST, ++inputPacketsLost);
					return FALSE;
				}
			}
    	
	    	wfi.wf.wFormatTag = WAVE_FORMAT_PCM;
	    	wfi.wf.nChannels = audioChannels;
	    	wfi.wf.nSamplesPerSec = samplesPerSecond;
	    	wfi.wf.nAvgBytesPerSec = bytesPerSecond;
	    	wfi.wf.nBlockAlign = sampleAlignment;
	    	wfi.wBitsPerSample = bitsPerSample;
	    	while (TRUE) {
		 		woo = waveOutOpen(&hWaveOut, waAudioOutDevice, (LPWAVEFORMATEX) &wfi,
		 								0L, 0L, WAVE_FORMAT_QUERY);
		 		if (bitsPerSample > 8 && woo == WAVERR_BADFORMAT) {
		 			audioIs8Bit = TRUE;
		 		} 
		 				
		 		/* If our preferred mode (16 bit, 11025 samples/second) isn't
		 		   supported, try falling back to bottom-feeder 8 bit per sample
		 		   mode. */			
		
		 		if ((audioUse8Bit || woo == WAVERR_BADFORMAT) && bitsPerSample > 8) {
		 			bitsPerSample /= 2;
		 			sampleAlignment /= 2;
		 			bytesPerSecond /= 2;	
			    	wfi.wf.nAvgBytesPerSec = bytesPerSecond;
			    	wfi.wf.nBlockAlign = sampleAlignment;
			    	wfi.wBitsPerSample = bitsPerSample;
			 		woo = waveOutOpen(&hWaveOut, waAudioOutDevice, (LPWAVEFORMATEX) &wfi,
			 				0L, 0L, WAVE_FORMAT_QUERY);
		 		}
		 		
		 		/* If we've failed to initialise in either 16 or 8 bit mode
		 		   at 8000 samples per second, it's possible the sound card
		 		   doesn't support any sampling mode below the Windows standard
		 		   of 11025 samples per second.  Have another go-round and see
		 		   if 11025 works. */
		 		
		 		if (woo == WAVERR_BADFORMAT && samplesPerSecond == 8000) {
		 			samplesPerSecond = 11025;
		 			bitsPerSample = 16;
		 			sampleAlignment = bitsPerSample / 8;
		 			bytesPerSecond = samplesPerSecond * sampleAlignment;  
		 		} else {
		 			break;
		 		}
		 	} 
	 		
	 		if (woo != 0) {				
		    	char et[MAXERRORLENGTH];
		    	
		    	waveOutGetErrorText(woo, et, sizeof et);
	        	MessageBox(hwnd, et, rstring(IDS_T_WAVE_PLAY_FORMAT_ERR),
			   		MB_OK | MB_ICONEXCLAMATION);
				return FALSE;
	    	}
		    if ((woo = waveOutOpen(&hWaveOut, waAudioOutDevice,
				  (LPWAVEFORMATEX) &wfi, (DWORD) (UINT) hwnd, 0, (DWORD) CALLBACK_WINDOW)) != 0) {
		    	char et[MAXERRORLENGTH];
		    	
		    	/* The next line looks wrong, doesn't it?  But I've seen drivers
		    	   for half-duplex sound boards that return NOTSUPPORTED instead
		    	   of ALLOCATED when you try to open input and output at the
		    	   same time. */
			    
			    if ((woo == MMSYSERR_ALLOCATED || woo == MMSYSERR_NOTSUPPORTED)
			    	&& inputActive) {
			    	/* Yuck.  The fact that we're receiving audio input
			     	   appears to have rendered the audio output port
			     	   busy.  Silently sacrifice the packet on the altar
			     	   of this most un-creative sound bobbler. */ 
					propeller(IDC_PH_INPUT_LOST, ++inputPacketsLost);
					return FALSE;
			    }	
		    	waveOutGetErrorText(woo, et, sizeof et);
		        MessageBox(hwnd, et, rstring(IDS_T_ERR_OPEN_WAVE_OUTPUT),
					MB_OK | MB_ICONEXCLAMATION);
				return FALSE;
		    }
		    outputActive = TRUE;
		    aboutOutSamples = samplesPerSecond;
		    aboutOutBits = bitsPerSample;
	    	propUpdateAudio();
		}
    }
    return TRUE;
}

//	createClient  --  Allocate and initialise client data for new connection

static LPCLIENT_DATA createClient(SOCKADDR_IN addrClient, soundbuf *d)
{
	LPCLIENT_DATA pClientData = (LPCLIENT_DATA) GlobalAllocPtr(GPTR, sizeof(CLIENT_DATA));
	if (pClientData != NULL) {
	    memset(pClientData, 0, sizeof(CLIENT_DATA));
	    pClientData->dwType = WINDOW_TYPE_CLIENT;
	    pClientData->wantsInput = FALSE;
	    pClientData->broadcastBeginTime = GetTickCount();
	    pClientData->broadcastEnd = FALSE;
	    pClientData->state = Embryonic;
	    pClientData->sReply = pClientData->sControl = INVALID_SOCKET;
		pClientData->timeout = 0;
		pClientData->sendSDEStimer = 0;
		pClientData->buttonUpTimer = FALSE;
		pClientData->inetSock = addrClient;
		pClientData->modemConnection = addrClient.sin_addr.s_addr == 0; 
	    pClientData->hFile = HFILE_ERROR;
	    pClientData->szFile[0] = '\0';
	    pClientData->face_stat = FSinit;
		pClientData->protocol = PROTOCOL_UNKNOWN;
		pClientData->localLoopback = IS_LOCALHOST(addrClient.sin_addr.s_addr);
		pClientData->llhead = pClientData->lltail = NULL;
		pClientData->uname = NULL;
		pClientData->email[0] = 0;
		pClientData->port = ntohs(addrClient.sin_port);
		pClientData->auxSock = NULL;
		pClientData->d_celp_ctx = NULL;
	    memcpy(pClientData->session_id, "\221\007\311\201", 4);
	    pClientData->face_bmp = NULL;
	    if (!pClientData->modemConnection) {
	    	/* Set host only on socket connections.  Leave dial string
	    	   blank for remote-initiated modem connections. */
	    	pClientData->szHost[0] = '(';
	    	if (d != NULL) {
				strcpy(pClientData->szHost + 1, d->sendinghost);
				strcat(pClientData->szHost, " ");
			}
			strcat(pClientData->szHost, inet_ntoa(pClientData->inetSock.sin_addr));
			strcat(pClientData->szHost, ")"); 
		}
	}
	return pClientData;
}

//	findPort  --  Find port socket is listening to from socket number

static unsigned short findPort(SOCKET s)
{
	struct auxSocket *as = asList;

	if (s == sControl || s == sCommand || s == INVALID_SOCKET) {
		return NETFONE_COMMAND_PORT;
	}
	
	while (as != NULL) {
		if ((as->asrefc > 0) && (s == as->asdata || s == as->asctrl)) {
			return as->asport;
		}
		as = as->asnext;
	}
	return 0;
}

//	socketInput  --  Process audio packet received on the inbound socket

static VOID socketInput(HWND hwnd, SOCKET sock, SOCKERR serr, SOCKEVENT sevent)
{
    int rll;
    SOCKADDR_IN addrClient;
    INT cbAddrClient;
    LPCLIENT_DATA pClientData = NULL;
    HWND hwndClient = NULL;
    soundbuf *d = &receivedSoundBuffer;
    int loopedBack = sock == INVALID_SOCKET; 
    static int errorBoxUp = FALSE;
    static int RecCount = 0;
    // Prevent audio deadlock
    int hdxPacketLost = (RecCount++ > 0) && (!loopedBack);
    int wasrtp = FALSE, ispurt = !outputActive, packetSeq;
    
    if (hdxPacketLost) {
//OutputDebugString("Yikes!!!\r\n");    
    	propeller(IDC_PH_INPUT_LOST, ++inputPacketsLost);
    }
    
    if (!waNetNoMsgLoopIns && !loopedBack) {
    	DefaultMessageLoop();
    }
    --RecCount;
    
	cbAddrClient = sizeof(addrClient);
    if (loopedBack) {
    
    	// Message from local loopback
		
	    rll = loop_recvfrom((LPCLIENT_DATA) MAKELONG(sevent, serr),
	    					(CHAR *) d, sizeof(soundbuf), 
	    					(SOCKADDR *) &addrClient,
							&cbAddrClient);
//xd(d, rll, TRUE);							
    } else {
#ifdef MODEM    
	    if (sevent == MODEM_INPUT_EVENT) {
	    	addrClient.sin_addr.s_addr = 0;
	    } else
#endif     
	    {
		    if ((sevent != FD_READ) || (serr != 0) || errorBoxUp) {
		        //  Unknown command or unknown event or socket error.  Ignore it.
		        return;
		    }
		
		    //  We got a packet.  Let's see what it tells us to do.
		
		    rll = recvfrom(sock, (CHAR *) d, sizeof(soundbuf), 0, 
		    					(SOCKADDR *) &addrClient,
								&cbAddrClient);
		}
	}
	
	if (rll == SOCKET_ERROR) {
//OutputDebugString("Data socket recvfrom error.\r\n");
		return;	
	}
	
	propeller(IDC_PH_PACKETS_RECEIVED, ++packetsReceived);
	
	if (closeBomb) {
		return;
	}
	
if (lwl_t_diactive) {
return;
}
//OutputDebugString("Data input\r\n");
//xd(d, rll, TRUE);    
    
    //  See if a connection window already exists for this host
    
    addrClient.sin_port = htons(findPort(sock));
    hwndClient = findClientByHost(&addrClient);

    //  Create the connection window data if no window exists
    
    if (hwndClient != NULL) {
        pClientData = CLIENTPTR(hwndClient);
	    if (pClientData->timeout > 0) {
	    	pClientData->timeout = 0;
	    }
    } else {
    
    	/* If the user closed a connection to this window within
    	   LazarusLength seconds ago, don't allow a packet from the
    	   other end to re-open it. */
    	   
    	if ((Lazarus == addrClient.sin_addr.s_addr) &&
    		(LazarusLong > 0)) {
    		return;
    	}
    
		if ((pClientData = createClient(addrClient, d)) == NULL) {
			return;
		}
	}
	
	if (pClientData->protocol == PROTOCOL_SPEAKFREE) {
	    revlong(&(d->compression));
		revlong(&(d->buffer.buffer_len));

		/* If this is a face data request packet, hand it off
		   to the face file retriever to to be filled with
		   appropriate data and return it to the requestor. 
		   Note that we do this before attempting to acquire audio
		   output.  This means that half duplex does not prevent our
		   responding to a face request whilst transmitting.
		   Theoretically, that is....  In reality, face data
		   can overload a precariously loaded 14.4 Kb line, and
		   lead to lockups with buggy Winsocks.  So, if we are
		   transmitting sound, we ignore the face request and
		   count on the timeout to pick up the transfer when we're
		   not sending. */	    
		    	
	    if ((pClientData->state != Embryonic) && !outputActive &&
	    	((d->compression & (fFaceData | faceRequest)) ==
	    		(fFaceData | faceRequest))) {
	    	int l;
		    	
	    	processFaceRequest(d);
	    	l = (int) d->buffer.buffer_len; 
		    revlong(&(d->compression));
			revlong(&(d->buffer.buffer_len));
	    	if (!useSendNotSendto) {
	    		int stat;
		    		
		        stat = sendto(pClientData->sReply, (LPSTR) d,
		        	l + (sizeof(soundbuf) - BUFL),
		            0, (LPSOCKADDR) &(pClientData->name), sizeof pClientData->name);
		        if (stat < 0) {
		        	useSendNotSendto = TRUE;
					if (hDlgPropeller != NULL) {
						SetDlgItemText(hDlgPropeller, IDC_PH_SENDTO, rstring(IDS_T_SEND));
					}
		        }
		    }
		    if (useSendNotSendto) {
		    	send(pClientData->sReply, (LPSTR) d, l + (sizeof(soundbuf) - BUFL), 0);
		    }
	    	propeller(IDC_PH_PACKETS_SENT, ++packetsSent);
	    	return;
	    }
	    
	    /*	If this is a face data reply, pass it to the mechanism
	    	that's assembling the face in memory.  */
	    	
	    if ((d->compression & fFaceData) && (hwndClient != NULL) && (pClientData != NULL)) {
	    	processFaceData(hwndClient, pClientData, d);
	    	return;
	    }
	    revlong(&(d->compression));
		revlong(&(d->buffer.buffer_len));
	}
	    
    //	Grab the audio device if we don't already have it
	
	outputTimeout = 0;					// Reset release output timeout    
    if (hdxPacketLost || !obtainOutput(hwnd)) {
    	hdxPacketLost = TRUE;
    	/* If we're broadcasting, we ignore the failure to obtain
    	   output due to half duplex hardware.  This allows remote
    	   users to blip us to subscribe to the broadcast. */
    	if (!broadcasting) {
    		goto FatalAudioExit;
    	}
    }
	    
    if (pClientData == NULL) {
        goto FatalExit;
    }
    
    if (pClientData->state == Embryonic) {
    
    	/* If our main window is iconic and we're establishing a
    	   new connection, restore the window so the user can see
    	   who's connecting.  "Look who's talking!"  */
    	   
		if (IsIconic(hwnd) && lookWho_sTalking) {
			ShowWindow(hwnd, SW_SHOWNORMAL);
			SetForegroundWindow(hwnd);
		}
    	//  Create a new connection child window
		hwndClient = createNewConnection(pClientData);
	}

    if (hwndClient != NULL) {
		pClientData->cbReceived += rll;
		if (pClientData->timeout > 0) {
			pClientData->timeout = 0;
		}
		
#ifdef CRYPTO		
		
        /* If a DES key is present and we're talking RTP or VAT
		   protocol we must decrypt the packet at this point.  */

		if (((pClientData->protocol == PROTOCOL_RTP) ||
			 (pClientData->protocol == PROTOCOL_VAT)) &&
			 pClientData->rtpdeskey[0]) {
			des_key_schedule sched;
			des_cblock ivec;
			int drll = rll;
			LPSTR whichkey;

			memset(ivec, 0, 8);
			drll = (rll + 7) & (~7);
			whichkey = pClientData->protocol == PROTOCOL_VAT ?
					   pClientData->vatdeskey : pClientData->rtpdeskey;
#ifdef RTP_DEBUG				
			{	char s[132];
				
                wsprintf(s, "Decrypting %d data bytes with %s key.\r\n",
						drll, (LPSTR)
						(whichkey == pClientData->vatdeskey ? "VAT" : "RTP"));
				OutputDebugString(s);
			}
#endif				
			if (drll > rll) {
				/* Should only happen for VAT protocol.  Zero the rest of
				   the DES encryption block to guarantee consistency. */
				memset(((char *) d) + rll, 0, drll - rll);
			}
			des_set_key((des_cblock *) (whichkey + 1), sched);
			des_ncbc_encrypt((des_cblock *) d,
				(des_cblock *) d, rll, sched,
				(des_cblock *) ivec, DES_DECRYPT);
#ifdef HEXDUMP
//xd(d, rll, TRUE);
#endif
		}
#endif		
		
		/* If this message is tagged with our Speak Freely protocol
		   bit, force protocol back to Speak Freely.  This allows us
		   to switch back to Speak Freely after receiving packets in
		   VAT.  We can still get confused if we receive a packet from
           an older version of Speak Freely that doesn't tag. */

		if (pClientData->protocol == PROTOCOL_VAT ||
			pClientData->protocol == PROTOCOL_VATRTP_CRYPT) {
			unsigned char *p = (unsigned char *) d;

			if (((p[0] >> 6) & 3) == 1) {
				pClientData->protocol = PROTOCOL_SPEAKFREE;
				InvalidateRect(hwndClient, NULL, TRUE);
#ifdef RTP_DEBUG
				OutputDebugString("fProtocol: Resetting protocol from VAT to Speak Freely.\r\n");
#endif				
			}
		}

		/* If this is a VAT packet, translate it into a sound buffer. */

		if (((pClientData->protocol == PROTOCOL_VAT)) &&
			(memcmp(((unsigned char *) d) + 2, pClientData->session_id, 2) == 0) &&
			isvat((unsigned char *) d, rll)) {
			if (d->buffer.buffer_len == 0) {
#ifdef RTP_DEBUG
                OutputDebugString("Ignoring unparseable VAT packet.\r\n");
#endif                
				return;
			}
			wasrtp = TRUE;

		/* If this is an RTP packet, transmogrify it into a sound
		   buffer we can understand. */

		} else if ((pClientData->protocol == PROTOCOL_RTP) &&
				 (memcmp(((unsigned char *) d) + 8, pClientData->session_id, 4) == 0) &&
				 isrtp((unsigned char *) d, rll)) {
			if (d->buffer.buffer_len == 0) {
#ifdef RTP_DEBUG
                OutputDebugString("Ignoring unparseable RTP packet.\r\n");
#endif                
				return;
			}
			wasrtp = TRUE;
		}
		
		if (!wasrtp) {
			LONG xbl;

			/* Convert relevant fields from network to host
			   byte order, if necessary. */

			d->compression = ntohl(d->compression);
			d->buffer.buffer_len = ntohl(d->buffer.buffer_len);

			/* If packet was in robust mode, extract the sequence
			   number from the length field and clear the sequence
			   number field to zero. */

			if (d->compression & fCompRobust) {
				packetSeq = (d->buffer.buffer_len >> 24) & 0xFF;
				d->buffer.buffer_len &= 0xFFFFFF;
			} 

			/* Now if this is a valid Speak Freely packet (as
			   opposed to a VAT packet masquerading as one, or
               an encrypted VAT or RTP packet we don't have the
			   proper key to decode), the length received from the
			   socket will exactly equal the buffer length plus
			   the size of the header.	This is a reasonably
			   good validity check, well worth it considering the
			   horrors treating undecipherable garbage as a sound
			   buffer could inflict on us. */

			xbl = d->buffer.buffer_len + (sizeof(struct soundbuf) - BUFL);

			/* If the encryption mode(s) used in this packet required
			   it to be padded to a frame length, adjust the expected
			   packet length to a frame boundary. */

			if ((d->compression & (fEncDES | fEncIDEA | fEncPGP | fEncBF)) != 0) {
				xbl = ((d->buffer.buffer_len + 7) & (~7)) +
					  (sizeof(struct soundbuf) - BUFL);
			}
			if ((d->compression & fEncAES) != 0) {
				xbl = ((d->buffer.buffer_len + 15) & (~15)) +
						(sizeof(struct soundbuf) - BUFL);
			}

            /* If packet is compressed with LPC-10 or CELP, compensate for "packet
               stuffing". */

			if ((d->compression & (fCompLPC10 | fCompCELP)) &&
				(d->buffer.buffer_len >= 16)) {
				xbl -= 16;
			}
			if (xbl != rll) {
#ifdef RTP_DEBUG 
				char s[132];
				
				wsprintf(s, "Sound buffer length %ld/%lX doesn't match %d byte packet.  Hdr=%08lX\r\n",
							xbl, d->buffer.buffer_len, rll, ntohl(d->compression));
				OutputDebugString(s);
//xd(d, rll, TRUE);				
				if (pClientData->protocol != PROTOCOL_UNKNOWN) {
                    wsprintf(s, "%s sending in unknown protocol or encryption.\r\n",
							(LPSTR) pClientData->szHost);
					OutputDebugString(s);
				}
#endif							
				pClientData->protocol = PROTOCOL_UNKNOWN;
				InvalidateRect(hwndClient, NULL, TRUE);
				return;
			}

			/* If does appear to be a genuine Speak Freely sound
			   buffer.	On that basis, set the protocol to Speak Freely
               even if the buffer isn't explicitly tagged. */

			if (pClientData->protocol != PROTOCOL_SPEAKFREE) {
#ifdef RTP_DEBUG
				char s[132];
				
                wsprintf(s, "%s sending in Speak Freely protocol.\r\n",
                    		(LPSTR) pClientData->szHost);
                OutputDebugString(s);
#endif
				pClientData->protocol = PROTOCOL_SPEAKFREE;
				InvalidateRect(hwndClient, NULL, TRUE);
			}
		}		
				
	    /*	If loopback is requested, bung it right back to the sender
	    	remembering, of course, first to clear the loopback bit.
	    	Loopback will never be set in packets translated from RTP
	    	or VAT, so we don't need to test for that here.  Also, make
	    	sure we don't send a loopback packet to the local loopback
	    	address itself--that can get really ugly.  */
	    	
	    if (((d->compression & fLoopBack) || bEchoPackets) && (!loopedBack)) {
	    	d->compression &= ~fLoopBack;
		    revlong(&(d->compression));				// *sigh*
	    	revlong(&(d->buffer.buffer_len));
	    	if (!useSendNotSendto) {
	    		int stat;
	    		
		        stat = sendto(pClientData->sReply, (LPSTR) d, rll,
		            0, (LPSOCKADDR) &(pClientData->name), sizeof pClientData->name);
		        if (stat < 0) {
		        	useSendNotSendto = TRUE;
					if (hDlgPropeller != NULL) {
						SetDlgItemText(hDlgPropeller, IDC_PH_SENDTO, rstring(IDS_T_SEND));
					}
		        }
		    }
		    if (useSendNotSendto) {
		    	send(pClientData->sReply, (LPSTR) d, rll, 0);
		    }
		    revlong(&(d->compression));
	    	revlong(&(d->buffer.buffer_len));
	    	propeller(IDC_PH_PACKETS_SENT, ++packetsSent);
	    }
	    
	    /*	If conference mode is in effect, iterate over all active connections
			and reflect the packet back to each of them skipping, of course,
			the connection whence it originated.  */
			
		if (bConferencing && isSoundPacket(d->compression)) {
			HWND hwndClient = GetWindow(hwndMDIClient, GW_CHILD);
			
			while (hwndClient != NULL) {
				if ((WNDPROC) GetWindowLong(hwndClient, GWL_WNDPROC) == ((WNDPROC) connectWndProc)) {
					LPCLIENT_DATA wClientData = CLIENTPTR(hwndClient);
				
					if ((wClientData != NULL) && IS_CLIENT_WINDOW(hwndClient) &&
					    (pClientData != wClientData)) {
						revlong(&(d->compression));				// *sigh*
	    				revlong(&(d->buffer.buffer_len));
	    				if (!useSendNotSendto) {
	    					int stat;
				    		
							stat = sendto(wClientData->sReply, (LPSTR) d, rll,
								0, (LPSOCKADDR) &(wClientData->name), sizeof wClientData->name);
							if (stat < 0) {
		        				useSendNotSendto = TRUE;
								if (hDlgPropeller != NULL) {
									SetDlgItemText(hDlgPropeller, IDC_PH_SENDTO, rstring(IDS_T_SEND));
								}
							}
						}
						if (useSendNotSendto) {
		    				send(wClientData->sReply, (LPSTR) d, rll, 0);
						}
						revlong(&(d->compression));
	    				revlong(&(d->buffer.buffer_len));
	    				propeller(IDC_PH_PACKETS_SENT, ++packetsSent);
					}
				}
				hwndClient = GetWindow(hwndClient, GW_HWNDNEXT);
			}
		}

        /* If this packet has been "stuffed" for maximum efficiency,
		   un-stuff it at this point. */

		if ((d->compression & (fCompLPC10 | fCompCELP)) &&
			(d->buffer.buffer_len >= 16)) {
			memcpy((char *) d + rll, d->sendinghost, 
				  sizeof d->sendinghost);
			rll += sizeof d->sendinghost;
		}

#ifdef OLDWAY

		/* If the packet is in "robust mode" validate the sequence number
		   against that of the connection and discard the packet if it
		   is duplicate or out of sequence. */

		if (d->compression & fCompRobust) {
			if (packetSeq == pClientData->rseq) {
				return;
			}
			 pClientData->rseq = packetSeq;
		}
#else
		if (d->compression & fCompRobust) {
			int pdist, mdist, cdist;

			/* Robust mode sends multiple copies of each sound buffer
			   with a sequence number modulo 256 hidden in the most
			   significant byte of the buffer length field.  If
			   multipath has caused packets to arrive out of order,
               we want do discard any packets prior one we've played;
               otherwise we'd shuffle the sound with the result being
			   gibberish.  The fact that the sequence number is modular
			   complicates this test a tad since the sequence numbers
			   wrap around at 255.	Consequently, we compute the
			   distance in modular space between the sequence number
               in the packet we've just received and that of the last
			   packet we played.  */

			if (packetSeq >= pClientData->rseq) {
				pdist = packetSeq - pClientData->rseq;
				mdist = (pClientData->rseq + 256) - packetSeq;
			} else {
				mdist = pClientData->rseq - packetSeq;
				pdist = (packetSeq + 256) - pClientData->rseq;
			}
			cdist = (pdist <= mdist) ? pdist : -mdist;

			if ((cdist <= 0) && (cdist > -16)) {
                cPrintf("Robust REJECTING   seq %3d   last seq %3d   distance %4d",
					packetSeq, pClientData->rseq, cdist);
				return;
			}
            cPrintf("Robust PLAYING     seq %3d   last seq %3d   distance %4d",
				packetSeq, pClientData->rseq, cdist);
			pClientData->rseq = packetSeq;
		}
#endif

		/*	If we're broadcasting, allow a site to drop the broadcast
			by blipping the mike BroadcastUnsubscribe seconds or later
			after the initial subscription.  (The delay prevents multiple
			packets from the original subscribe or unsubscribe request
			from flipping the subscription back and forth.)  */

		if (broadcasting) {
			if ((!pClientData->broadcastEnd) &&
				((GetTickCount() - pClientData->broadcastBeginTime) >
					(BroadcastUnsubscribe * 1000L))) {
				pClientData->broadcastEnd = TRUE;
				pClientData->broadcastBeginTime = GetTickCount();
			} 
		}

		/*	If this is a PGP-encrypted session key, determine if
			we need to decrypt it and put it into effect.  We disable
			this during a broadcast or conference so users can't bring us to a
			screeching halt by sending a PGP key request.  */
			
		if ((d->compression & fKeyPGP) && !broadcasting && !bConferencing) {
#ifdef CRYPTO		
			pgpSetSessionKey(hwndClient, pClientData, d);
#endif			
		} else {
		
			/*	If the sending host offers us a face image and
				we haven't already undertaken its retrieval, start
				up the process to fetch it in the background.  If
				the source address is a multicast group (yes, I know
				it shouldn't be, but given the things I've seen
				purported implementations of multicast do, I wouldn't
				be surprised at anything), we ignore the proffered
				face out of fear we'll unleash a paroxysm of face
				swapping. */
				
			if (faceShow && (d->compression & fFaceOffer) &&
				(pClientData->face_stat == FSinit)) {
				if (IN_MULTICAST(pClientData->inetSock.sin_addr.s_addr)) {
					pClientData->face_stat = FSabandoned;
				} else {
					connFetchFace(hwndClient, pClientData);
				}
			}		
	    
			//	Okay, now we're ready to play the sound buffer
	
			if (!hdxPacketLost) {
			
				/* If this is the first packet of a talk spurt and
				   anti-jitter has been enabled, place audio in anti-
				   jitter pause and start the timer which will resume
				   output after the pause expires. */
				   
				if (ispurt && (jitterBuf > 0) && (jitterTimer == 0)) {
					jitterTimer = SetTimer(hwnd, JITTER_TIMER_ID, jitterBuf, NULL);
					if (jitterTimer != 0) {
						waveOutPause(hWaveOut);
						jitterPause = TRUE;
//OutputDebugString("Pause\r\n");						
					} 
				}
					
				pClientData->state = PlayingReceivedAudio;
				playSound(hwndClient, pClientData, d, bitsPerSample, samplesPerSecond);
			}
//else OutputDebugString("Ouch!!\r\n");			
		}
        return;
    }

    //  Unable to create connection window

    if (pClientData != NULL) {
        GlobalFreePtr(pClientData);
    }

FatalExit:
    if (hwndClient != NULL) {
        FORWARD_WM_MDIDESTROY(hwndMDIClient, hwndClient, SendMessage);
    }

FatalAudioExit:
	errorBoxUp = FALSE;
}

//	controlInput  --  Process control packet received on the control socket

#ifdef MSVC5OPT_BUG
#pragma optimize("", off)
#endif
static VOID controlInput(HWND hwnd, SOCKET sock, SOCKERR serr, SOCKEVENT sevent)
{
    int rll;
    SOCKADDR_IN addrClient;
    INT cbAddrClient;
    LPCLIENT_DATA pClientData = NULL;
    HWND hwndClient = NULL;
    soundbuf *d = &receivedSoundBuffer;
	unsigned char *p = (unsigned char *) d;

	cbAddrClient = sizeof(addrClient);
    if (sock == INVALID_SOCKET) {
    
    	// Message from local loopback
		
	    rll = loop_recvfrom((LPCLIENT_DATA) MAKELONG(sevent, serr),
	    					(CHAR *) d, sizeof(soundbuf), 
	    					(SOCKADDR *) &addrClient,
							&cbAddrClient);
    } else {
	    if ((sevent != FD_READ) || (serr != 0)) {
	        //  Unknown command or unknown event or socket error.  Ignore it.
	        return;
	    }
		
	    //  We got a packet.  Let's see what it tells us to do.
		
	    rll = recvfrom(sock, (CHAR *) d, sizeof(soundbuf), 0, 
	    				  (SOCKADDR *) &addrClient, &cbAddrClient);
    }
    
	
	if (rll == SOCKET_ERROR) {
//OutputDebugString("Control socket recvfrom error.\r\n");
		return;	
	}    
	propeller(IDC_PH_PACKETS_RECEIVED, ++packetsReceived);
	
	if (closeBomb) {
		return;
	}
	
if (lwl_t_diactive) {
return;
}

if (inputActive) {
return;
}
//OutputDebugString("Control input\r\n");
//xd(d, rll, TRUE);
    
    //  See if a connection window already exists for this host
    
    addrClient.sin_port = htons(findPort(sock));
    hwndClient = findClientByHost(&addrClient);

    //  Create the connection window data if no window exists
    
    if (hwndClient != NULL) {
        pClientData = CLIENTPTR(hwndClient);
    } else {
    
    	/* If the user closed a connection to this window within
    	   LazarusLength seconds ago, don't allow a packet from the
    	   other end to re-open it. */
    	   
    	if ((Lazarus == addrClient.sin_addr.s_addr) &&
    		(LazarusLong > 0)) {
    		return;
    	}
    
    	/* Do not permit a VAT IDLIST packet to open a new connection.
    	   You can join a conference only by explicitly opening a
    	   connection to it.  If we allowed an IDLIST to open a
    	   connection, an IDLIST received after we disconnected
    	   would pop the connection back into life.  Also, just on
    	   general principles, it's a poor idea to allow a BYE to
    	   open a new connection.  So...we allow remote VAT protocol
    	   connections to be initiated only by an unencrypted ID
    	   (1) packet. */

    
    	if ((((p[0] >> 6) & 3) == 0) && (p[1] != 1)) {
    		return;
    	}
    	
		if ((pClientData = createClient(addrClient, NULL)) == NULL) {
			return;
		}
	}
	    
    if (pClientData == NULL) {
        goto FatalExit;
    }
    
    if (pClientData->state == Embryonic) {
    
    	/* If our main window is iconic and we're establishing a
    	   new connection, restore the window so the user can see
    	   who's connecting.  "Look who's talking!"  */
    	   
		if (IsIconic(hwnd) && lookWho_sTalking) {
			ShowWindow(hwnd, SW_SHOWNORMAL);
			SetForegroundWindow(hwnd);
		}
    	//  Create a new connection child window
		hwndClient = createNewConnection(pClientData);
	}

    if (hwndClient != NULL) {
		pClientData->cbReceived += rll;
		if (pClientData->timeout > 0) {
			pClientData->timeout = 0;
		}
#ifdef RTP_DEBUG		
{ char s[132];
  wsprintf(s, "Control message from %s: %d bytes.\r\n", (LPSTR) pClientData->szHost, rll);
  OutputDebugString(s);
}
#endif

#ifdef CRYPTO

        /* If a DES key is present and we're talking RTP or VAT
		   protocol we must decrypt the packet at this point. */

		if (pClientData->rtpdeskey[0]) {

			/* One more little twist.  Since this packet arrived on the
			   control channel, see if it passes all the tests for a
               valid RTCP packet.  If so, we'll assume it isn't
			   encrypted.  RTP utilities have the option of either
			   encrypting their control packets or sending them in
			   the clear, so a hack like this is the only way we have
			   to guess whether something we receive is encrypted. */

			if (!isValidRTCPpacket((unsigned char *) d, rll)) {
				des_key_schedule sched;
				des_cblock ivec;
				int drll = rll;
				LPSTR whichkey;
				static int toggle = 0;

				memset(ivec, 0, 8);
				drll = (rll + 7) & (~7);
				if (drll > rll) {
					/* Should only happen for VAT protocol.  Zero the rest of
					   the DES encryption block to guarantee consistency. */
					memset(((char *) d) + rll, 0, drll - rll);
				}
				if (pClientData->protocol == PROTOCOL_UNKNOWN || 
					pClientData->protocol == PROTOCOL_VATRTP_CRYPT ||
					pClientData->protocol == PROTOCOL_SPEAKFREE) {
					whichkey = toggle == 0 ? pClientData->vatdeskey :
							   (toggle == 1 ? pClientData->rtpdeskey : NULL);
					toggle = (toggle + 1) % 3;
				} else {
					whichkey = pClientData->protocol == PROTOCOL_VAT ?
								pClientData->vatdeskey : pClientData->rtpdeskey;
				}
#ifdef RTP_DEBUG				
				{	char s[132];
				
                    wsprintf(s, "Decrypting %d VAT/RTP control bytes with %s key.\r\n",
							drll, (LPSTR) (whichkey == NULL ? "clear" :
							(whichkey == pClientData->vatdeskey ? "VAT" : "RTP")));
					OutputDebugString(s);					
				}
#endif				
				if (whichkey != NULL) {
					des_set_key((des_cblock *) (whichkey + 1), sched);
					des_ncbc_encrypt((des_cblock *) d,
						(des_cblock *) d, rll, sched,
						(des_cblock *) ivec, DES_DECRYPT);
	
					/* Just one more thing.  In RTP (unlike VAT), when
					   an RTCP control packet is encrypted, 4 bytes of
					   random data are prefixed to the packet to prevent
					   known-plaintext attacks.  We have to strip this
					   prefix after decrypting. */
	
					if ((*(((char *) d) + 4) & 0xC0) == 0x80) {
						rll -= 4;
						memmove((char *) d, ((char *) d) + 4, rll);
					}					
				}
			}
//xd(d, rll, TRUE);			
		}
#endif		

		/* Dispatch the packet to the appropriate handler for its protocol. */

		{
			int protocol = PROTOCOL_VATRTP_CRYPT;
			int proto = (p[0] >> 6) & 3;

			if ((proto == 0) && !waProtNoVAT) {
				/* To avoid spoofing by bad encryption keys, require
                   a proper ID message be seen before we'll flip into
				   VAT protocol. */
				if (((p[1] == 1) || (p[1] == 3)) ||
					((pClientData->protocol == PROTOCOL_VAT) && (p[1] == 2))) {
					protocol = PROTOCOL_VAT;
					memcpy(pClientData->session_id, p + 2, 2);  /* Save conference ID */
					
					if (p[1] == 1) {
						char uname[256];

						memcpy(uname, p + 4, rll - 4);
						uname[rll - 4] = 0;
						if ((pClientData->uname == NULL) ||
							(strcmp(uname, pClientData->uname) != 0)) {
							if (pClientData->uname != NULL) {
								GlobalFreePtr(pClientData->uname);
								pClientData->uname = NULL;
							}
							pClientData->uname = (LPSTR) GlobalAllocPtr(GPTR, strlen(uname) + 1);
							if (pClientData->uname != NULL) {
								strcpy(pClientData->uname, uname);
								pClientData->email[0] = 0;
								InvalidateRect(hwndClient, NULL, TRUE);
								SetWindowText(hwndClient, uname[0] ? uname
									: (pClientData->localLoopback ?
										rstring(IDS_T_LOOPBACK) : pClientData->szHost));
							}
						}
					}
  
					/* Handling of VAT IDLIST could be a lot more elegant
					   than this. */

					if (p[1] == 3) {
						LPSTR uname;

						uname = GlobalAllocPtr(GPTR, rll);
						if (uname != NULL) {
							unsigned char *bp = p, *ep = p + rll;
							int i = bp[4];

							bp += 8;
							uname[0] = -1;
							uname[1] = 0;
							*ep = 0;
							while (--i >= 0 && bp < ep) {
								unsigned long id = *(unsigned long *) bp;

								bp += 4;
								strcat(uname, (char *) bp);
								while (isspace(uname[strlen(uname) - 1])) {
									uname[strlen(uname) - 1] = 0;
								}
                                strcat(uname, ", ");
								bp += (strlen((char *) bp) + 4) & ~3;
							}
							if (strlen(uname) > 1) {
								uname[strlen(uname) - 2] = 0;
							}
							if ((pClientData->uname == NULL) ||
								(strcmp(uname, pClientData->uname) != 0)) {
								if (pClientData->uname != NULL) {
									GlobalFreePtr(pClientData->uname);
									pClientData->uname = NULL;
								}
								pClientData->uname = uname;
								pClientData->email[0] = 0;
								InvalidateRect(hwndClient, NULL, TRUE);
								//	For conference, keep host name in title bar
								SetWindowText(hwndClient, pClientData->localLoopback ?
										rstring(IDS_T_LOOPBACK) : pClientData->szHost);
							} else {
								GlobalFreePtr(uname);
							}
						}
					}

                    /* If it's a DONE packet, reset protocol to unknown. */

					if (p[1] == 2) {
						pClientData->protocol = protocol = PROTOCOL_UNKNOWN;
						if (pClientData->uname != NULL) {
							GlobalFreePtr(pClientData->uname);
							pClientData->uname = NULL;
						}
						pClientData->email[0] = 0;
						InvalidateRect(hwndClient, NULL, TRUE);
						SetWindowText(hwndClient, (pClientData->localLoopback ?
								rstring(IDS_T_LOOPBACK) : pClientData->szHost));
#ifdef RTP_DEBUG
						{	char s[132];						
		                    wsprintf(s, "VAT DONE message from %s.\r\n",
									 (LPSTR) pClientData->szHost);
							OutputDebugString(s);
						}
#endif
					}
				}
			} else if (((proto == RTP_VERSION) && !waProtNoRTP) || proto == 1) {
				if (isValidRTCPpacket((unsigned char *) d, rll)) {
					unsigned char *apkt;

					protocol = (proto == 1) ? PROTOCOL_SPEAKFREE : PROTOCOL_RTP;;
					memcpy(pClientData->session_id, p + 4, 4);  /* Save SSRC */

                    /* If it's a BYE packet, reset protocol to unknown. */

					if (isRTCPByepacket((unsigned char *) d, rll)) {
						pClientData->protocol = protocol = PROTOCOL_UNKNOWN;
						pClientData->uname = NULL;
						pClientData->email[0] = 0;
						InvalidateRect(hwndClient, NULL, TRUE);
						SetWindowText(hwndClient, (pClientData->localLoopback ?
								rstring(IDS_T_LOOPBACK) : pClientData->szHost));
#ifdef RTP_DEBUG
						{	char s[132];						
		                    wsprintf(s, "RTCP BYE message from %s.\r\n",
									 (LPSTR) pClientData->szHost);
							OutputDebugString(s);
						}
#endif

					//	Check for RTCP APP packet containing text chat

					} else if (isRTCPAPPpacket((unsigned char *) d, rll,
								RTCP_APP_TEXT_CHAT, &apkt) && (apkt != NULL)) {
						char *ident = pClientData->szHost;

						if (pClientData->email[0] != 0) {
							ident = pClientData->email;
						}
						if (pClientData->uname != NULL) {
							ident = pClientData->uname;
						}
						chatLog(hwnd, ident, (char *) (apkt + 12));

					//	Otherwise this is presumably an SDES packet identifying the sender

					} else {
						struct rtcp_sdes_request rp;

						rp.nitems = 4;
						rp.item[0].r_item = RTCP_SDES_CNAME;
						rp.item[1].r_item = RTCP_SDES_NAME;
						rp.item[2].r_item = RTCP_SDES_EMAIL;
						rp.item[3].r_item = RTCP_SDES_TOOL;
						if (parseSDES((unsigned char *) d, &rp)) {
							char uname[256], email[256], program[256];

							uname[0] = email[0] = 0;
							if (rp.item[1].r_text != NULL) {
								copySDESitem(rp.item[1].r_text, uname);
								if (rp.item[2].r_text != NULL) {
									copySDESitem(rp.item[2].r_text, email);
								} else if (rp.item[2].r_text != NULL) {
									copySDESitem(rp.item[0].r_text, email);
								}
							} else if (rp.item[2].r_text != NULL) {
								copySDESitem(rp.item[2].r_text, uname);
							} else if (rp.item[0].r_text != NULL) {
								copySDESitem(rp.item[0].r_text, uname);
							}
							if (rp.item[3].r_text != NULL) {
								copySDESitem(rp.item[3].r_text, program);
							}
							if ((pClientData->uname == NULL) ||
								(strcmp(uname, pClientData->uname) != 0)) {
								if (pClientData->uname != NULL) {
									GlobalFreePtr(pClientData->uname);
									pClientData->uname = NULL;
								}
								pClientData->uname = (LPSTR) GlobalAllocPtr(GPTR, strlen(uname) + 1);
								if (pClientData->uname != NULL) {
									strcpy(pClientData->uname, uname);
									InvalidateRect(hwndClient, NULL, TRUE);
									strcpy(pClientData->email, email);
									SetWindowText(hwndClient, uname[0] ? uname
										: (pClientData->localLoopback ?
											rstring(IDS_T_LOOPBACK) : pClientData->szHost));
								}
							}
							if (strcmp(email, pClientData->email) != 0) {
								strcpy(pClientData->email, email);
								InvalidateRect(hwndClient, NULL, TRUE);
							} else if (strcmp(program, pClientData->szRemoteProgram) != 0) {
								strcpy(pClientData->szRemoteProgram, program);
								InvalidateRect(hwndClient, NULL, TRUE);
							}
						} else {
#ifdef RTP_DEBUG				
                    		OutputDebugString("Invalid RTCP packet received.\r\n");
#endif					
						}
					}
				}
			} else {
#ifdef RTP_DEBUG				
                OutputDebugString("Bogus protocol 3 control message.\r\n");
#endif					
			}

			/* If protocol changed, update in connection. */

			if (protocol != pClientData->protocol) {
				pClientData->protocol = protocol;
				if (protocol == PROTOCOL_VATRTP_CRYPT) {
					pClientData->uname = NULL;
					pClientData->email[0] = 0;
				}
#ifdef RTP_DEBUG
				{	char s[132];
					static char *pname[] = {
	                    "Speak Freely",
	                    "VAT",
	                    "RTP",
	                    "VAT/RTP encrypted",
	                    "Unknown"
					};
                    wsprintf(s, "%s sending in %s protocol.\r\n",
							 (LPSTR) pClientData->szHost, (LPSTR) pname[protocol]);
					OutputDebugString(s);
				}
#endif
				InvalidateRect(hwndClient, NULL, TRUE);				
			}
		}
        return;
    }

    //  Unable to create connection window

    if (pClientData != NULL) {
        GlobalFreePtr(pClientData);
    }

FatalExit:
    if (hwndClient != NULL) {
        FORWARD_WM_MDIDESTROY(hwndMDIClient, hwndClient, SendMessage);
    }
}
#ifdef MSVC5OPT_BUG
#pragma optimize("", on)
#endif

#ifdef MODEM

//	MODEMINPUT  --  Finite state machine for processing modem input

static int modemInputState = 0;

static void modemInput(HWND hwnd)
{
	char modembuf[256];
	int l;
	char *cp = modembuf;
	static LPSTR op;
	static short len, ilen;
	static unsigned short bcrc;
	COMSTAT cs;
	
	int err = GetCommError(modemHandle, &cs);
	if (err != 0) {
		MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(16), err);
		errorRant(hwnd);
	}
	l = ReadComm(modemHandle, modembuf, sizeof modembuf);
	err = GetCommError(modemHandle, &cs);
	if (err != 0) {	
		MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(17), err);
		errorRant(hwnd);
		return;
	}
    
	while (l-- > 0) {
		char ch = *cp++;
		
		switch (modemInputState) {
			case 0:
				if (ch == 1) {
					if (modemSb == NULL) {
						modemSb = (LPSTR) GlobalAllocPtr(GPTR, sizeof(soundbuf) + 12);
						if (modemSb == NULL) {
							return;
						}
					}
					modemInputState++;
					op = (LPSTR) modemSb;
					break;
				}
				continue;
				
			case 1:
			case 2:
			case 3:
				if (ch == (modemInputState + 1)) {
					modemInputState++;
				} else {
					modemInputState = 0;
					continue;
				}
				break;
				
			case 4:
			case 5:
			case 6:
				modemInputState++;
				break;
			
			case 7:
				/* Look at the length specification and its checksum
				   inverted copy and proceed only if it's plausible. */
				len = *((short *) (op - 3));
				revshort(&len);
				ilen = (((unsigned short) op[-1]) << 8) | ((unsigned char) ch);
				if (len < (sizeof(soundbuf) - BUFL) ||
					len > sizeof(soundbuf) || len != ~ilen) {
					modemInputState = 0;
					continue;	
				}
				ilen = len;
				modemInputState++;
				break;
			
			//	Receiving sound buffer
				
			case 8:
				if (--len <= 0) {
					modemInputState++;
				}
				break;
				
			//	Receiving two CRC bytes
				
			case 9:
			case 10:
				modemInputState++;
				if (modemInputState < 11) {
					break;
				}
				//	Wheeee!!!  Note fall-through.
				
			//	Complete buffer received
			
			case 11:
				modemInputState = 0;		// Reset state machine
				bcrc = crc(modemSb, ilen + 4 + 2 * sizeof(unsigned short));
				if (((bcrc >> 8) != (unsigned char) op[-1]) || ((bcrc & 0xFF) != (unsigned char) ch)) {
					/*	CRC error.  Probably should have options in the
						modem options dialogue to discard CRC error buffers
						or try to play them regardless.  */
					MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(18));
					errorRant(hwnd);
				}
				memcpy(&receivedSoundBuffer,
					modemSb + 4 + 2 * sizeof(unsigned short), ilen);  
				socketInput(hwnd, 0, 0, MODEM_INPUT_EVENT);
				continue;
		}
		*op++ = ch;
	}
	GetCommEventMask(modemHandle, EV_RXCHAR);                    
}
#endif

// PARSECOMMANDLINE  --  Parse a command line

void ParseCommandLine(HWND hwnd, LPSTR pszCmdLine)
{
    if (pszCmdLine != NULL) {
    	LPSTR cln, clp = pszCmdLine, hostip;
    	
    	while (strlen(clp) > 0) {
    		while (strlen(clp) > 0 && isspace(*clp)) {
    			clp++;
    		}
    		if (*clp == 0) {
    			break;
    		}
    		cln = strchr(clp, ',');
	    	if (cln != NULL) {
	    		*cln = 0;
	    	}
	    	if ((*clp != '-') && (*clp != '/')) {
				int isip = FALSE;
	    		char *cp;
	    		long iport = 0;
				char cb[MAX_HOST];
				
				hostip = clp;
				strcpy(cb, clp);
				if ((cp = strchr(cb, '/')) != NULL ||
		    		(cp = strchr(cb, ':')) != NULL) {
		    		
		    		iport = atol(cp + 1);
		    		*cp = 0;
				}
				if (iport >= 0 && (inet_addr(cb) != INADDR_NONE)) {
					clp = NULL;
				} else {
					hostip = NULL;
				}
				newConnection(hwnd, clp, hostip);
	    	}
	    	if (cln == NULL) {
	    		break;
	    	}
	    	clp = cln + 1;
	    }
    }
}

//  Frame_WndProc  --  Main frame window procedure

LRESULT CALLBACK Frame_WndProc(HWND hwnd, UINT nMessage, WPARAM wParam,
                               LPARAM lParam)
{
	int i;
	char conn[12];
	
    switch (nMessage) {
    
        HANDLE_MSG(hwnd, WM_COMMAND, onCommand);
        HANDLE_MSG(hwnd, WM_CREATE, onCreate); 
        HANDLE_MSG(hwnd, WM_SOCKET_SELECT, socketInput);
        
        case WM_NEW_COMMANDLINE:
        	ParseCommandLine(hwnd, (LPSTR) lParam);
        	return 0;
        	
        case WM_SOCKET_CONTROL:
        	controlInput(hwnd, (SOCKET) (wParam), (SOCKERR) WSAGETSELECTERROR(lParam),
        				(SOCKEVENT) WSAGETSELECTEVENT(lParam));
        	return 0;
        	
        case WM_SHOWWINDOW:
			/*	If this is the first time our main window is being displayed,
				see if the window position and display mode (normal, maximised, etc.)
				has been saved from a previous execution.  If so, restore the same
				modes for the initial presentation of the window this time.  If the
				window was left minimised, however, we ignore that and display it
				normally, since many users may fail to notice the application
				popping up solely on the bozo bar.  (Unless, of course, the user has
				requested we use a tray icon and explicitly *asked* us to start
				minimised, in which case we comply.  */
			{
				static int firstTime = TRUE;
				
				if (firstTime) {
					char cg[80];
					firstTime = FALSE;
				  
					GetPrivateProfileString(rstring(IDS_PF_GEOMETRY), rstring(IDS_PF_WINDOWPLACEMENT), "", cg,
    						sizeof cg, rstring(IDS_PF_PROFILE_FILE));
					if (strlen(cg) > 0) {
						WINDOWPLACEMENT wp;
						
						if (sscanf(cg, Format(80), &(wp.flags), &(wp.showCmd),
		    								&(wp.ptMinPosition.x), &(wp.ptMinPosition.y),
		    								&(wp.ptMaxPosition.x), &(wp.ptMaxPosition.y),
		    								&(wp.rcNormalPosition.left), &(wp.rcNormalPosition.top),
		    								&(wp.rcNormalPosition.right), &(wp.rcNormalPosition.bottom)) == 10) {
							wp.length = sizeof(WINDOWPLACEMENT);
							
							/*	If the window was minimised or in some other screwball state,
								restore it to the last normal window display.  */
								
							if (wp.showCmd != SW_SHOWMAXIMIZED) {
								wp.showCmd = SW_SHOWNORMAL;
							}
							
							/*	But...if the user has requested that we start minimised, override
								the previous mode to honour the user's preference.  */
								
							if (startMinimised) {
								wp.showCmd = SW_SHOWMINIMIZED;
							}
							SetWindowPlacement(hwnd, &wp);
						}
					}
					
					/*	Oh, and of course if we're using a system tray icon and the user has
						requested we hide our taskbar item, hide the window initially.  Now
						of course this idiot PostMessage makes the taskbar first display the
						application button, then immediately hide it, Microsoft "now you see it,
						now you don't" style, but all of the other obvious approaches, such
						as calling ShowWindow here, passing SW_HIDE to the SetWindowPlacement
						call above, or using SW_HIDE in the WM_CREATE handler don't work.  */
						
					if (lurkInTray && hideWhenMinimised && startMinimised) {
						PostMessage(hwnd, WM_SIZE, SIZE_MINIMIZED, 0);
					}
				}
			}
			return 0;
			
#ifdef TRAY
		case WM_SIZE:
			if (lurkInTray && hideWhenMinimised &&
				(wParam == SIZE_MINIMIZED)) {
				ShowWindow(hwnd, SW_HIDE);
				return 0;
			}
			break;
#endif

#ifdef MODEM        
        case WM_COMMNOTIFY:
        	if (modemHandle != -1) {
        		switch (LOWORD(lParam)) {
        			case CN_RECEIVE:
        				modemInput(hwnd);
        				break;
        				
        			case CN_TRANSMIT:
		        		GetCommEventMask(modemHandle, EV_TXEMPTY);
		        		break;
		        		
		        	case CN_EVENT:
		        		{
		        			UINT stat = GetCommEventMask(modemHandle, CN_EVENT);
		        			COMSTAT lps;
		        			int err;
		        			
		        			if (stat == EV_ERR) {
			        			err = GetCommError(modemHandle, &lps);
//			        			if (err & (CE_RXOVER | CE_OVERRUN)) { 
//			        				FlushComm(modemHandle, 1);
//			        			}
//			        			FlushComm(modemHandle, 0);
	/*		        			
			        			if (err == CE_OVERRUN) {
			        				return 0;				// Nothing we can do about it!
			        			}
	*/		        			
			        			if (err != 0) {
									MsgBox(NULL, MB_ICONSTOP | MB_OK,
									        Format(19), err);
									errorRant(hwnd);
			        			}
		        			}
		        			stat = 0;
		        		}
		        		return 0;
        		}
        	} 
        	break;
#endif        	
        	
        case WM_CLOSE:
        	if (!closeBomb && (inputActive || outputActive)) {
        		if ((bombTimer = SetTimer(hwnd, BOMB_TIMER_ID, 250, NULL)) != 0) {
	        		closeBomb = TRUE;
				    terminateWaveInput();
				    waveOutShutdown();
				}
			}
			if (!closeBomb) {
				DestroyWindow(hwnd);
			}
			return 0;	
        
        case WM_DESTROY:
			{
			    HWND hConn;
			
				if (hwndMDIClient != NULL) {
					hConn = GetWindow(hwndMDIClient, GW_CHILD);
					
					/*	What's this all about, you ask?  Well, Windows, with its
			    		unerring instinct for doing things in the most idiotic
			    		way possible, sends a WM_DESTROY to the application's
			    		outer window procedure, and then *later* sends WM_DESTROY
			    		to each of the child windows.  Suppose one or more of
			    		those child windows need to do something--send a BYE
			    		message, for example--using one of the resources that
			    		get freed by the application's outermost WM_DESTROY?
			    		Blooie.  But don't think you can get away with just
			    		sending WM_DESTROY to each of the child windows: nopey,
			    		nopey, no.  If you try that you fall into the toilet
			    		because calling WM_DESTROY doesn't actually make them
			    		go away, and as a result they get destroyed twice and
			    		all kinds of other horrors ensue.  So, once again we
			    		are forced into subterfuge by the quintessential
			    		inelegance of Windows.  We dig out of this particular hole
			    		by sending a custom WM_CLEAN_UP_YOUR_ACT message to the
			    		child window to tell it we're terminating.  The child
			    		will then do its regular WM_DESTROY cleanup, including
			    		releasing the client data pointer and zeroing the
			    		window word to it.   When the actual WM_DESTROY arrives,
			    		it will discover the client data pointer is zero and
			    		avoid executing the cleanup twice. */
				
					while (hConn != NULL) {
			    		if ((WNDPROC) GetWindowLong(hConn, GWL_WNDPROC) ==
			    									((WNDPROC) connectWndProc)) {
			    			SendMessage(hConn, WM_CLEAN_UP_YOUR_ACT, 0, 0L);
			        		hConn = GetWindow(hConn, GW_HWNDNEXT);
						}
					}
				}
			}
		    terminateWaveInput();		// Terminate wave input if active
		    if (outputActive) {
				V waveOutReset(hWaveOut);
				V waveOutClose(hWaveOut);
				outputActive = FALSE;
			}
        	KillTimer(hwnd, FRAME_TIMER_ID); // Kill main timeout timer
        	if (jitterTimer != 0) {
        		KillTimer(hwnd, jitterTimer);
        	}
        	if (bombTimer != 0) {
        		KillTimer(hwnd, bombTimer);
        	}

		    gsm_destroy(gsmh);			// Shut down GSM decoding
		    lpc_end();					// Shut down LPC decoder
			if (celp_xp != NULL) {		// Release CELP transmit context if allocated
				GlobalFreePtr(celp_xp);
			}
#ifdef CRYPTO
			desdone();					// Close DES encryption
#endif
		    terminateWaveInput();		// Terminate wave input if active
		    answerClose();				// Close the answering machine
		    closeFaceFile();			// Close face image file
#ifdef MODEM		    
		    closeModem(hwnd);			// Modem à dodo
		    if (modemSb != NULL) {
		    	GlobalFreePtr(modemSb);	// Release modem buffer
		    }
#endif		    
			waveOutShutdown();			// Close wave audio output
#ifdef IP_MAX_MEMBERSHIPS			
			multicastJoin(NULL, FALSE);	// Drop all multicast group memberships
#endif			
			
			//	If an incomplete LWL periodic update is pending, terminate it
			
			if (lwlsock != INVALID_SOCKET) {
				SOCKET s = lwlsock;
				
				lwlsock = INVALID_SOCKET;
				shutdown(s, 2);
				closesocket(s);
				lwl_t_pending = FALSE;
			}
			if (lwl_t_published) {
				sendLwlMessage(hwnd, TRUE);	// Send BYE message to Look Who's Listening
			}
			
			//	Close all auxiliary sockets and release aux socket items
			
			while (asList != NULL) {
				struct auxSocket *s = asList;
				
				asList = s->asnext;
				if (s->asrefc > 0) {
					ResetSocket(s->asdata);
					ResetSocket(s->asctrl);
				}
				GlobalFreePtr(s); 
			}
			
#ifdef TRAY
			if (trayIconWindow != NULL) {
				DestroyWindow(trayIconWindow);
				trayIconWindow = NULL;
			}
#endif
			
			SetCursor(NULL);			// Make sure we don't destroy current cursor
			if (phoneCursor != NULL) {	
		    	DestroyCursor(phoneCursor);	// Release custom cursors
		    }
		    if (earCursor != NULL) {
		    	DestroyCursor(earCursor);
		    }
		    if (boltCursor != NULL) {
		    	DestroyCursor(boltCursor);
		    }

			/*	Update the profile file with the settings as of the end
				of execution.  If something blew up before we loaded the
				settings successfully, skip it in order not to destroy
				the old settings.  */
		    
		    if (createSuccessful) { 
		    	char pfn[80], cg[80];
		    	WINDOWPLACEMENT wp;
		    	
		    	strcpy(pfn, rstring(IDS_PF_PROFILE_FILE));
		    	
		    	//	Save window geometry and modes
		    	
		    	wp.length = sizeof(WINDOWPLACEMENT);
		    	GetWindowPlacement(hwndMDIFrame, &wp);
		    	wsprintf(cg, Format(80), wp.flags, wp.showCmd,
		    				 wp.ptMinPosition.x, wp.ptMinPosition.y,
		    				 wp.ptMaxPosition.x, wp.ptMaxPosition.y,
		    				 wp.rcNormalPosition.left, wp.rcNormalPosition.top,
		    				 wp.rcNormalPosition.right, wp.rcNormalPosition.bottom);
		    	WritePrivateProfileString(rstring(IDS_PF_GEOMETRY), rstring(IDS_PF_WINDOWPLACEMENT),
		    		cg, pfn);
			    
			    //	Save remembered connections
			    
			    wsprintf(conn, Format(5), rememberedConnections);
			    strcpy(cg, rstring(IDS_PF_CONNECTIONS)); 
			    WritePrivateProfileString(cg, rstring(IDS_PF_CONNCOUNT),
			    	conn, pfn); 
			    for (i = 0; i < rememberedConnections; i++) {
			    	
			    	wsprintf(conn, Format(2), i + 1);
			    	WritePrivateProfileString(cg, conn,
			    		rememberedConnection[i], pfn); 
			    	GlobalFreePtr(rememberedConnection[i]);
			    }
			      
#ifdef IP_MAX_MEMBERSHIPS			    
			    
			    //	Save multicast group memberships
			    
			    wsprintf(conn, Format(5), multiMemberships);
			    /*	Must re-fetch IDS_PF_MULTIGROUPS each time since there can be
			    	sufficient multicast subscriptions to wrap rstring() circular
			    	buffer. */
			    WritePrivateProfileString(rstring(IDS_PF_MULTIGROUPS),
			    	rstring(IDS_PF_CONNCOUNT), conn, pfn);
			    for (i = 0; i < multiMemberships; i++) {
			    	
			    	wsprintf(conn, Format(3), i + 1);
			    	WritePrivateProfileString(rstring(IDS_PF_MULTIGROUPS), conn,
			    		inet_ntoa(multiAddr[i]), pfn); 
			    	wsprintf(conn, Format(4), i + 1);
			    	WritePrivateProfileString(rstring(IDS_PF_MULTIGROUPS), conn,
			    		multiName[i] == NULL ? "" : multiName[i],
			    		pfn);
			    	if (multiName[i] != NULL) { 
			    		GlobalFreePtr(multiName[i]);
			    	}
			    }
			    WritePrivateProfileString(rstring(IDS_PF_MULTIGROUPS), rstring(IDS_PF_LOOPBACK),
			    	multiLoop ? kS1 : kS0, pfn);
#endif			    	
			    	
			    //	Save compression modes
			    
#define BoolProfile(var, section, itemname, defval) WritePrivateProfileString(section, itemname, var ? kS1 : kS0, pfn)     
				strcpy(cg, rstring(IDS_PF_COMPRESSION));
			    BoolProfile(compression, cg, rstring(IDS_PF_COMP_SIMPLE), FALSE);
			    BoolProfile(gsmcompress, cg, rstring(IDS_PF_COMP_GSM), TRUE);
			    BoolProfile(adpcmcompress, cg, rstring(IDS_PF_COMP_ADPCM), FALSE);
			    BoolProfile(lpccompress, cg, rstring(IDS_PF_COMP_LPC), FALSE);
			    BoolProfile(lpc10compress, cg, rstring(IDS_PF_COMP_LPC10), FALSE);
			    BoolProfile(celpcompress, cg, rstring(IDS_PF_COMP_CELP), FALSE);
			    
			    //	Save protocol
			    
				wsprintf(conn, Format(5), protocolXmit);
			    WritePrivateProfileString(rstring(IDS_PF_PROTOCOL), rstring(IDS_PF_PROTOCOL_XMIT),
			    	conn, pfn);
			    BoolProfile(protocolAuto, cg, rstring(IDS_PF_PROTOCOL_SEND_RECEIVED), TRUE);
				wsprintf(conn, Format(5), robust);
			    WritePrivateProfileString(rstring(IDS_PF_PROTOCOL), rstring(IDS_PF_ROBUST),
			    	conn, pfn);

				//	Save encryption modes
				
				WritePrivateProfileString(rstring(IDS_PF_ENCRYPTION_MODES), rstring(IDS_PF_PUBLIC_KEY),
			    	rstring(pk_package == PGP ? IDS_PF_PUBLIC_KEY_PGP : IDS_PF_PUBLIC_KEY_GPG), pfn);							    

#ifdef MODEM
			    
			    //	Save modem configuration
			    
			    strcpy(cg, rstring(IDS_PF_MODEM));
	    		BoolProfile(modemEnable, cg, rstring(IDS_PF_ENABLE), FALSE);
			    WritePrivateProfileString(cg, rstring(IDS_PF_PORT),
			    	commport, pfn); 
			    WritePrivateProfileString(cg, rstring(IDS_PF_BAUDRATE),
			    	baudrate, pfn); 
			    WritePrivateProfileString(cg, rstring(IDS_PF_MODEM_INIT_STRING),
			    	modemInitString, pfn); 
				BoolProfile(modemShowRant, cg, rstring(IDS_PF_SHOW_RANT), TRUE);
#endif				
				
				//	Save answering machine settings
				
				strcpy(cg, rstring(IDS_PF_ANSWER));
				WritePrivateProfileString(cg, rstring(IDS_PF_FILE_NAME),
	    				answerFileName, pfn);
				WritePrivateProfileString(cg, rstring(IDS_PF_OUT_FILE_NAME),
	    				answerOutFileName, pfn);
	   			BoolProfile(answerRecord, cg, rstring(IDS_PF_RECORD_MESSAGES), FALSE);			
   	
				//	Save in your face settings
				
				WritePrivateProfileString(rstring(IDS_PF_FACE), rstring(IDS_PF_FILE_NAME),
						faceFileName, pfn);
			   	BoolProfile(faceShow, rstring(IDS_PF_FACE), rstring(IDS_PF_SHOW_FACES), TRUE);	
				
				//	Save preferences
	
			    WritePrivateProfileString(rstring(IDS_PF_RING),
			    	rstring(IDS_PF_FILE_NAME), ringFileName, pfn);
				wsprintf(conn, Format(5), jitterBuf);
                WritePrivateProfileString(rstring(IDS_PF_PREFERENCES),
                	rstring(IDS_PF_JITTER), conn, pfn);
                	
                //	Save window display preferences
                
//	    		BoolProfile(lookWho_sTalking, rstring(IDS_PF_PREFERENCES), rstring(IDS_PF_LOOKWHO), FALSE);
#define WindowPref(var, prefName)	BoolProfile(var, rstring(IDS_PF_DISPLAY_MODES), rstring(IDS_PF_OPT_ ## prefName), FALSE)
				WindowPref(lookWho_sTalking, LOOK_WHO);
				WindowPref(openOnAnswerMessage, OPEN_ON_ANSWER_MESSAGE);
				WindowPref(lurkInTray, USE_TRAY_ICON);
				WindowPref(hideWhenMinimised, HIDE_WHEN_MINIMISED);
				WindowPref(startMinimised, START_MINIMISED);
#undef WindowPref
                
				//	Save Look Who's Listening parameters
				
				WritePrivateProfileString(rstring(IDS_PF_LWL_TELL),
					rstring(IDS_PF_SERVER), lwl_s_server, pfn);
				WritePrivateProfileString(rstring(IDS_PF_LWL_TELL),
					rstring(IDS_PF_EMAIL), lwl_s_email, pfn);
				WritePrivateProfileString(rstring(IDS_PF_LWL_TELL),
					rstring(IDS_PF_FULLNAME), lwl_s_fullname, pfn);
				WritePrivateProfileString(rstring(IDS_PF_LWL_TELL),
					rstring(IDS_PF_PHONE), lwl_s_phone, pfn);
				WritePrivateProfileString(rstring(IDS_PF_LWL_TELL),
					rstring(IDS_PF_LOCATION), lwl_s_location, pfn);
			    BoolProfile(lwl_s_publish, rstring(IDS_PF_LWL_TELL), rstring(IDS_PF_PUBLISH), FALSE);
    			BoolProfile(lwl_s_exact, rstring(IDS_PF_LWL_TELL), rstring(IDS_PF_EXACT), FALSE);
			
				WritePrivateProfileString(rstring(IDS_PF_LWL_ASK),
					rstring(IDS_PF_SERVER), lwl_a_server, pfn);
			    BoolProfile(lwl_a_exact, rstring(IDS_PF_LWL_ASK), rstring(IDS_PF_EXACT), FALSE);
	    		
	    		//	Save workarounds
			    
			    strcpy(cg, rstring(IDS_PF_WORKAROUNDS));	
			    BoolProfile(alwaysBindSocket, cg, rstring(IDS_PF_ALWAYS_BIND), FALSE);	
			    BoolProfile(waNetNoConnect, cg, rstring(IDS_PF_NET_NO_CONNECT), FALSE);	
			    BoolProfile(waNetUseSend, cg, rstring(IDS_PF_NET_USE_SEND), FALSE);	
			    BoolProfile(waNetMultiTTLisChar, cg, rstring(IDS_PF_MULTICAST_CHAR_ARG), FALSE);	
    			BoolProfile(waNetNoOutOverflow, cg, rstring(IDS_PF_NO_OUT_OVERFLOW), FALSE);	
    			BoolProfile(waNetNoMsgLoopIns, cg, rstring(IDS_PF_NO_MSG_LOOP), FALSE);
				wsprintf(conn, Format(5), waNetSynchronousGetHostname);
			    WritePrivateProfileString(cg, rstring(IDS_PF_SYNC_HOSTNAME), conn, pfn);			    
    
			    BoolProfile(waProtNoHeartbeat, cg, rstring(IDS_PF_WA_NO_HEARTBEAT), FALSE);    
			    BoolProfile(waProtUseLargerRTCPackets, cg, rstring(IDS_PF_WA_RTP_LARGER_PKTS), FALSE);    
			    BoolProfile(waProtNoVAT, cg, rstring(IDS_PF_WA_NO_VAT), FALSE);    
			    BoolProfile(waProtNoRTP, cg, rstring(IDS_PF_WA_NO_RTP), FALSE);    
			    BoolProfile(waProtNoRTCPCrypt, cg, rstring(IDS_PF_WA_NO_RTCP_CRYPT), FALSE);    
    	
			    BoolProfile(waAudioHalf, cg, rstring(IDS_PF_AUDIO_HALF_DUPLEX), FALSE);	
			    BoolProfile(waAudio11025, cg, rstring(IDS_PF_AUDIO_SAMPLE_11025), FALSE);	
    			BoolProfile(waAudioRingMaxVolume, cg, rstring(IDS_PF_RING_VOLUME), FALSE);	
    			BoolProfile(waAudioNoOutputRateAdjustment, cg, rstring(IDS_PF_NO_OUTPUT_RATE_ADJUSTMENT), FALSE);

				// Save the RLE and VOX parameters
				save_vox_params();

				//	Save audio monitor parameters
				
			    strcpy(cg, rstring(IDS_PF_MONITOR));	
			    BoolProfile(spectrumBarGraph, cg, rstring(IDS_PF_MON_BARGRAPH), TRUE);
			    BoolProfile(spectrumVoicePrint, cg, rstring(IDS_PF_MON_VOICEPRINT), FALSE);
			    BoolProfile(spectrumTransmitOnly, cg, rstring(IDS_PF_MON_TRANSMIT), FALSE);
			    BoolProfile(spectrumReceiveOnly, cg, rstring(IDS_PF_MON_RECEIVE), FALSE);
				BoolProfile(spectrumMaxEnergy, cg, rstring(IDS_PF_MON_ENV_MAX), FALSE);

#undef BoolProfile
			}

			/*	Close the sockets we were using to listen for input
				from the network on the data and control ports.  */

			ResetSocket(sCommand);
			ResetSocket(sControl);
		    
		    //  Disconnect from the sockets library before terminating
		
		    WSACleanup();
		    
			//	Shut down the Help subsystem

            if (holped) {
#ifdef HTML_HELP
				HtmlHelp(NULL, NULL, HH_UNINITIALIZE, HtmlHelpContext);
#else
            	WinHelp(hwnd, rstring(IDS_HELPFILE), HELP_QUIT, 0L);
#endif
            }

			//	Release LWL and RTP SDES, and VAT ID packets, if allocated

			if (sdes != NULL) {
				free(sdes);
			}
			if (rtpsdes != NULL) {
				free(rtpsdes);
			}
			if (vatid != NULL) {
				free(vatid);
			}

			//	That's all, folks

		    PostQuitMessage(0);
		    return 0L;

		case WM_NCDESTROY:
			hwndMDIClient = NULL;
			break;
		    
		case WM_DROPFILES:
			{
				char dropConn[MAX_PATH];
				UINT nfiles = DragQueryFile((HDROP) wParam, 0xFFFF,  dropConn, sizeof dropConn),
					 fn; 
				
				for (fn = 0; fn < nfiles; fn++) {
					DragQueryFile((HDROP) wParam, fn, dropConn, sizeof dropConn);
					newConnection(hwnd, dropConn, NULL);
				}
				DragFinish((HDROP) wParam);
			}
			break;
 
#ifdef ZZZ
 		case WM_QUERYNEWPALETTE:
   			{
 				HWND hActive;

 				hActive = (HWND) LOWORD(SendMessage(hwndMDIClient, WM_MDIGETACTIVE, 0, 0L));

 				if (hActive != NULL) {
            		return SendMessage(hActive, WM_QUERYNEWPALETTE, hwnd, 0L);
            	}

 				return FALSE;
 			}
#endif 			
       
        case WM_INITMENU:
        	{	HMENU menu = (HMENU) wParam;
        	    int connActive = FALSE;
				HWND hWndClient = getActiveConnection();
				LPCLIENT_DATA pClientData;
				HMENU connect;
				
				/*	Tack recent remembered connections to the end of
					the "Connect" menu.  */
				
				connect = GetSubMenu(GetMenu(hwnd), 0);
				if (connect != NULL) {
					int i;
					
					for (i = 0; i <= REMEMBER_CONNECTIONS; i++) {
						//	BoundsChecker users--it's cool; this API failure is intentional
						DeleteMenu(connect, IDM_CUSTOM + i, MF_BYCOMMAND);
					}
					if (rememberedConnections > 0) {
						AppendMenu(connect, MF_SEPARATOR, IDM_CUSTOM, NULL); 
						for (i = 0; i < rememberedConnections; i++) {
							AppendMenu(connect, MF_STRING, IDM_CUSTOM + i + 1,
								rememberedConnection[i]);	
						} 
					}
				}
				if (hWndClient != NULL) {
					connActive = TRUE;
					pClientData = CLIENTPTR(hWndClient);
				}
#define Enable(item, condition) EnableMenuItem(menu, (item), MF_BYCOMMAND | \
			((condition) ? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
#define Checker(item, value) CheckMenuItem(menu, (item), MF_BYCOMMAND | \
			((value) ? MF_CHECKED : MF_UNCHECKED)) 			
			
				Enable(IDM_CONN_PROPERTIES, connActive);
				/* Strictly speaking, ring is only supported by Speak Freely
				   protocol, but we allow it for other protocols purely as a
				   shortcut way to send the ring audio file.  Now that ring's
				   setting audio volume to maximum has been disabled in self
				   defence, Speak Freely doesn't by default treat a ring any
				   differently than an incoming audio file anyway. */
				Enable(IDM_CONN_RING, connActive && pClientData->hFile == HFILE_ERROR &&
					pClientData->mmioHandle == NULL);
				Enable(IDM_SEND_SOUND_FILE, connActive && pClientData->hFile == HFILE_ERROR &&
					pClientData->mmioHandle == NULL);
				Enable(IDM_DISCONNECT, connActive);
				Enable(IDM_CONN_SAVE, connActive && pClientData->connectionFileName[0]);
				Enable(IDM_CONN_SAVE_AS, connActive);
#ifndef IP_MAX_MEMBERSHIPS
				Enable(IDM_CONN_MULTICAST, FALSE);
#endif				
				Enable(IDM_CO_RECORD, answerEnabled());
				Checker(IDM_CO_RECORD, answerEnabled() && answerRecord);
				Checker(IDM_CONN_BROADCAST, broadcasting);
				Checker(IDM_CONN_CONFERENCE, bConferencing);	// Auto check conference. -BCW 11/30/1997
				Checker(IDM_CONN_ECHO, bEchoPackets);			// Auto check echo. -BCW 10/03/1998
				
				//	Window display options

				Enable(IDM_OPT_HIDE_WHEN_MINIMISED, lurkInTray);	// Mustn't hide unless tray icon enabled
#define WindowPref(var, prefName)	Checker(IDM_OPT_ ## prefName, var)
				WindowPref(lookWho_sTalking, LOOK_WHO);
				WindowPref(openOnAnswerMessage, OPEN_ON_ANSWER_MESSAGE);
				WindowPref(lurkInTray, USE_TRAY_ICON);
				WindowPref(hideWhenMinimised, HIDE_WHEN_MINIMISED);
				WindowPref(startMinimised, START_MINIMISED);
#undef WindowPref
							
				//	Jitter compensation settings
			
#define Jitter(time) Checker(IDM_OPT_JITTER_##time, jitterBuf == time)
		        Jitter(0);
				Jitter(100); 
				Jitter(250); 
				Jitter(500); 
				Jitter(750); 
				Jitter(1000); 
				Jitter(2000); 
				Jitter(3000);
#undef Jitter		 
				
				//	Encryption mode settings
				
				Checker(IDM_PUBLICKEYPACKAGE_PGP, pk_package == PGP);
				Checker(IDM_PUBLICKEYPACKAGE_GPG, pk_package == GPG);
				
				//	Workarounds can often be changed only when no connection active

#define WorkA(item, value) Enable(item, !connActive); Checker(item, value)				
				WorkA(IDM_WORKA_BIND, alwaysBindSocket);
				WorkA(IDM_WORKA_NOCONNECT, waNetNoConnect);
				WorkA(IDM_WORKA_USE_SEND, waNetUseSend);
				WorkA(IDM_WORKA_TTLCHAR, waNetMultiTTLisChar);
				WorkA(IDM_WORKA_OUT_OVERFLOW, waNetNoOutOverflow);
				Checker(IDM_WORKA_MSG_LOOP, waNetNoMsgLoopIns);
				Checker(IDM_WORKA_SYNC_HOSTNAME, waNetSynchronousGetHostnameAction);
				
			    Checker(IDM_WORKA_NOHB, waProtNoHeartbeat);
			    WorkA(IDM_WORKA_BIGRTP, waProtUseLargerRTCPackets); 				
			    Checker(IDM_WORKA_NOVAT, waProtNoVAT); 				
			    Checker(IDM_WORKA_NORTP, waProtNoRTP); 
			    Checker(IDM_WORKA_NORTCP_CRYPT, waProtNoRTCPCrypt);			
				
				WorkA(IDM_WORKA_HALF_DUPLEX, waAudioHalf);
				WorkA(IDM_WORKA_SAMPLE_11025, waAudio11025);
#undef WorkA
				Checker(IDM_WORKA_RING_VOLUME, waAudioRingMaxVolume);
				Checker(IDM_WORKA_DISABLE_AORA, waAudioNoOutputRateAdjustment);
				
 				Checker(IDM_VOX_FAST, voxmode == IDM_VOX_FAST);
 				Checker(IDM_VOX_MEDIUM, voxmode == IDM_VOX_MEDIUM);
 				Checker(IDM_VOX_SLOW, voxmode == IDM_VOX_SLOW);
 				Checker(IDM_VOX_NONE, voxmode == IDM_VOX_NONE);
				Enable(IDM_VOX_MONITOR, !IsVoxMonitorOn());
 				
				//	Compression can be changed only when input is idle
				
				Enable(IDM_COMP_2X, !inputActive && protocolSent == PROTOCOL_SPEAKFREE);
 				Checker(IDM_COMP_2X, compression);
				Enable(IDM_COMP_GSM, !inputActive);
 				Checker(IDM_COMP_GSM, gsmcompress);
				Enable(IDM_COMP_ADPCM, !inputActive);
 				Checker(IDM_COMP_ADPCM, adpcmcompress);
				Enable(IDM_COMP_LPC, !inputActive);
 				Checker(IDM_COMP_LPC, lpccompress);
				Enable(IDM_COMP_LPC10, !inputActive && protocolSent == PROTOCOL_SPEAKFREE);
 				Checker(IDM_COMP_LPC10, lpc10compress);
				Enable(IDM_COMP_CELP, !inputActive && protocolSent == PROTOCOL_SPEAKFREE);
 				Checker(IDM_COMP_CELP, celpcompress);
 				
 				Enable(IDM_PROT_SPEAKFREE, !inputActive);
 				Checker(IDM_PROT_SPEAKFREE, protocolXmit == PROTOCOL_SPEAKFREE);
 				Enable(IDM_PROT_RTP, !inputActive);
 				Checker(IDM_PROT_RTP, protocolXmit == PROTOCOL_RTP);
 				Enable(IDM_PROT_VAT, !inputActive);
 				Checker(IDM_PROT_VAT, protocolXmit == PROTOCOL_VAT);

				Enable(IDM_PROT_ROBUST1, !inputActive && (protocolXmit == PROTOCOL_SPEAKFREE));
				Enable(IDM_PROT_ROBUST2, !inputActive && (protocolXmit == PROTOCOL_SPEAKFREE));
				Enable(IDM_PROT_ROBUST3, !inputActive && (protocolXmit == PROTOCOL_SPEAKFREE));
				Enable(IDM_PROT_ROBUST4, !inputActive && (protocolXmit == PROTOCOL_SPEAKFREE));
				Enable(IDM_PROT_ROBUST5, !inputActive && (protocolXmit == PROTOCOL_SPEAKFREE));
				Enable(IDM_PROT_ROBUST6, !inputActive && (protocolXmit == PROTOCOL_SPEAKFREE));
				Enable(IDM_PROT_ROBUST7, !inputActive && (protocolXmit == PROTOCOL_SPEAKFREE));
				Enable(IDM_PROT_ROBUST8, !inputActive && (protocolXmit == PROTOCOL_SPEAKFREE));
				Checker(IDM_PROT_ROBUST1, (protocolXmit == PROTOCOL_SPEAKFREE) && robust == 1);
				Checker(IDM_PROT_ROBUST2, (protocolXmit == PROTOCOL_SPEAKFREE) && robust == 2);
				Checker(IDM_PROT_ROBUST3, (protocolXmit == PROTOCOL_SPEAKFREE) && robust == 3);
				Checker(IDM_PROT_ROBUST4, (protocolXmit == PROTOCOL_SPEAKFREE) && robust == 4);
				Checker(IDM_PROT_ROBUST5, (protocolXmit == PROTOCOL_SPEAKFREE) && robust == 5);
				Checker(IDM_PROT_ROBUST6, (protocolXmit == PROTOCOL_SPEAKFREE) && robust == 6);
				Checker(IDM_PROT_ROBUST7, (protocolXmit == PROTOCOL_SPEAKFREE) && robust == 7);
				Checker(IDM_PROT_ROBUST8, (protocolXmit == PROTOCOL_SPEAKFREE) && robust == 8);
 					
				//	Audio must be idle to change break mode
				
				Enable(IDM_OPT_BREAK, !inputActive && !outputActive && halfDuplex);
 				Checker(IDM_OPT_BREAK, breakinput && halfDuplex);
 				
				//	Audio must be idle to change 8/16 bit mode
				
				Enable(IDM_OPT_8BIT, !audioIs8Bit && !inputActive && !outputActive);
 				Checker(IDM_OPT_8BIT, (audioUse8Bit || audioIs8Bit));
 				
 				//	Face image transfer supported only by Speak Freely protocol
 				
 				Enable(IDM_OPT_FACE, protocolXmit == PROTOCOL_SPEAKFREE);
 				
 				/*	Can't access LWL server when input active (because it
 					could lead to a reentrant call to WINSOCK.  */
 					
 				Enable(IDM_DIR_LISTING, !inputActive);
 				Enable(IDM_DIR_SEARCH, !inputActive); 				

#ifdef MODEM
 					
 				//	Can't change modem configuration while modem session active
 				
 				Enable(IDM_OPT_MODEM, modemSessions == 0);
#endif 				
 				if (connActive) {
	 				ModifyMenu(GetSubMenu(menu, 0), IDM_SEND_SOUND_FILE,
	 					MF_BYCOMMAND | MF_STRING | MF_ENABLED, IDM_SEND_SOUND_FILE,
	 					(pClientData->hFile == HFILE_ERROR &&
	 						pClientData->mmioHandle == NULL) ? rstring(IDS_T_SEND_SOUND_MENU) :
	 						rstring(IDS_T_STOP_SOUND_MENU));
 				}

				//	Disable modeless dialogue launch items if dialogue already shown

 				Enable(IDM_PROPELLER_HEAD, hDlgPropeller == NULL);
 				Enable(IDM_SPECTRUM, hDlgSpectral == NULL);
				Enable(IDM_TEXT_CHAT, hDlgChat == NULL); 	  
				Enable(IDM_HELP_CONSOLE, hDlgConsole == NULL); 	  
 				Enable(IDM_CO_REPONDEUR, hDlgAnswer == NULL); 	  
        	}
#undef Checker        	
#undef Enable        	
        	break;
        	
        case WM_SOCKET_LWL:
        	if (WSAGETSELECTEVENT(lParam) == FD_CONNECT) {
				if (send(lwlsock, (char *) sdes, sdesl, 0) < 0) {
			        int serr = WSAGetLastError();
					SOCKET s = lwlsock;
						
					lwlsock = INVALID_SOCKET;
					shutdown(s, 2);
					closesocket(s);
					if (serr != WSAEINPROGRESS) {				            
				        MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(56),
				                serr, SockerrToString(serr));
	        	        lwl_t_published = FALSE;
        	        }
        	    } else {
        	    	/* We don't dare count on Winsock to handle linger mode on
        	    	   a close correctly, and there's no notification when a
        	    	   non-blocking send() completes, so we trigger closing of
        	    	   the socket based on a timer. */
        	    	lwl_t_tock = 8;
           	    }
        	}
			break;

        case WM_TIMER:
            if (wParam == BOMB_TIMER_ID && closeBomb) {
            	if (outputPending == 0) {
            		DestroyWindow(hwnd);
            	}
            	return 0;
            }
        	
        	if (wParam == JITTER_TIMER_ID) {
        		KillTimer(hwnd, JITTER_TIMER_ID);
        		jitterTimer = 0;
        		if (jitterPause) {
        			jitterPause = FALSE;
        			if (outputActive) {
        				waveOutRestart(hWaveOut);
        			}
        		}
        		return 0;
        	}
        
			{
			    HWND hwndChild;
			
			    hwndChild = GetWindow(hwndMDIClient, GW_CHILD);
			    
			    while (hwndChild != NULL) {
			        if (GetWindow(hwndChild, GW_OWNER) == NULL) {
			            FORWARD_WM_TIMER(hwndChild, wParam, PostMessage);
			        }
			
			        hwndChild = GetWindow(hwndChild, GW_HWNDNEXT);
			    }
			    
#ifdef MODEM			    
			    
			    /* Now this is thoroughly idiotic place to initialise
			       the modem at start-up time, isn't it?   Why do it
			       here?  Because if you do it at the logical place, in
				   WM_CREATE, it doesn't work.  No error return anywhere
				   along the way, of course--this is Windows, after all,
				   but the port is as dead as the weight of Microsoft
				   on the struggling developer. */
			    
			    if (modemEnable && modemHandle == -1) {
			    	if (!openModem(hwnd)) {
			    			modemEnable = FALSE;
			    	}
			    }
#endif			    
			}
			
			//  If the Lazarus timer is running, decrement it.
			
			if (LazarusLong > 0) {
				LazarusLong--;
			}
			
			/*	If TIMEOUT_AUDIO_OUTPUT seconds have elapsed since we
				sent the last buffer to audio output, shut down audio
				output so as to make it available to other applications.  */
				
			if (outputActive && outputPending == 0 &&
				((++outputTimeout >= TIMEOUT_AUDIO_OUTPUT) || inputPaused)) {
				HWND hwndChild;

				answerSync();
        
				if (answerRecord) {  //Is answering machine turned on?
					if (answerOutFileName[0] != 0) { // Record even if no outgoing message.
						LPCLIENT_DATA pClientData;

							/* After we've received the message, send back our outgoing greeting message.
							   This method is still experimental.  We might need a better method of determining when
							   the caller is done leaving a message.  -BCW 08/10/1997 */
							hwndChild = GetWindow(hwndMDIClient, GW_CHILD); //Get handle of active child.
				        
						if (hwndChild != NULL) {
							pClientData = CLIENTPTR(hwndChild);

							if (pClientData != NULL) {
								if (!pClientData->bSentOutgoingMessage) {  // Did we already tell them we were out?
									/* Why set the flag that we sent the outgoing message already before
									   we actually send it?  That's because this is in a W_TIMER
									   handler, and if we popped up a message box if there was an error
									   reading the file, a new message would pop up every second, until
									   the user clicked OK on one of them.  Well, if the user is away,
									   chances are that it would bring Windows to its knees before they
									   returned, and we all know that Windows is not very friendly to
									   resources. */
									pClientData->bSentOutgoingMessage = TRUE; // Remember for next time.
									startSoundFile(hwndChild, answerOutFileName); // Play the outgoing message the user selected.
								}
							}
						}
					}
				}
				waveOutShutdown();
			}

			if (!outputActive && inputPaused) {
				V startWaveInput(hwnd);
				inputPaused = FALSE;
			}
			
			/*	If we're publishing Look Who's Listening information and
				we're approaching the timeout interval for the server,
				re-send our information to avoid being timed out.  */
			
			if (lwl_t_published && !lwl_t_diactive) {
				if (--lwl_t_resend <= 0) {
#ifdef BLOCK_SOCK					
					if ((lwl_t_published = sendLwlMessage(hwnd, FALSE)) != FALSE) {
						lwl_t_resend = TIMEOUT_RESEND_LWL;	// Set resend timer
					}
#else 

					/* We want to do this update in asynchronous mode so
					   as not to interfere with sound traffic.  Create the
					   socket, place it into async mode, and initiate the
					   connection.  We'll proceed with the subsequent steps
					   when we receive notification of the connection.
					   
					   First of all, we want to be sure the last periodic
					   update isn't still pending.  If so, that indicates
					   things are hung up, so we'll disable LWL for the
					   test of this session. */
					   
					if (lwlsock != INVALID_SOCKET) {
						SOCKET s = lwlsock;
						
						lwlsock = INVALID_SOCKET;
						shutdown(s, 2);
						closesocket(s);
						lwl_t_published = FALSE;
						MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(58));
						lwl_t_pending = FALSE;
						lwl_t_resend = 5;
					} else {
						lwlsock = socket(AF_INET, SOCK_STREAM, 0);
						if (lwlsock == INVALID_SOCKET) {
					        int serr = WSAGetLastError();
							
							if (serr != WSAEINPROGRESS) {				            
						        MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(54),
						                serr, SockerrToString(serr));
								lwl_t_published = FALSE;
							} else {
								lwl_t_resend = 30;
							}
						} else {
							int ling = TRUE, kaboom = FALSE;
							
							kaboom = 
								WSAAsyncSelect(lwlsock, hwnd, WM_SOCKET_LWL, FD_CONNECT) == SOCKET_ERROR;
//							if (!kaboom) {				  
//								kaboom =
//									setsockopt(lwlsock, SOL_SOCKET, SO_DONTLINGER, (char *) &ling, sizeof ling) == SOCKET_ERROR;
//							}
							if (!kaboom) {
								kaboom =
									connect(lwlsock, (struct sockaddr *) &(lookhost), sizeof lookhost) == SOCKET_ERROR;
								if (kaboom) {
									if (WSAGetLastError() == WSAEWOULDBLOCK) {
										kaboom = FALSE;
									}
								}					            

							}
							if (kaboom) {
								int serr = WSAGetLastError();
								
								if (serr != WSAEWOULDBLOCK && serr != WSAEINPROGRESS) {					            
								    MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(56),
								            serr, SockerrToString(serr));
									lwl_t_published = FALSE;
								} else {
									lwl_t_resend = 30;
								}
							    closesocket(lwlsock);
							    lwlsock = INVALID_SOCKET;
								lwl_t_pending = FALSE;
							} else {
								lwl_t_resend = TIMEOUT_RESEND_LWL;	// Reset resend timer
								lwl_t_pending = TRUE;
							}
						}
					}
#endif					
				}						
			}
			
			if (lwl_t_tock > 0) {
				if (--lwl_t_tock <= 0) {
					if (lwlsock != INVALID_SOCKET) {
					    closesocket(lwlsock);
					    lwlsock = INVALID_SOCKET;
					    lwl_t_tock = 0;
					    if (lwl_t_pending == 2) {
					    	lwl_t_resend = 2;
					    }
					    lwl_t_pending = FALSE;
					}
				}
			}	 
        	break;
        	
        /*	Input sound buffer received.  Pass it on to the output
        	handler of every client window that presently says it
        	wants input.  */
        
		case MM_WIM_DATA:
			{
				LPWAVEHDR isb;
				static LPARAM qsb = 0;
			    HWND hwndClient = GetWindow(hwndMDIClient, GW_CHILD);
			    static int RecCount = 0;
			    int bOverRun;
			    
			    // Prevent overrun and deadlock
			    
			    bOverRun = (RecCount++ > 0);
			    if (bOverRun && !inputTerm) {

					//	Attempt to queue buffer for later processing in loop

					if (qsb == 0) {
						qsb = lParam;
						RecCount--;
						break;						 
					}

					/*	No go; there's a buffer already queued.	 Before
						we discard this buffer, it must be placed back into
						the pool for wave input.  */

					isb = (LPWAVEHDR) lParam;
					Assert(currentInputLength <= inputSoundBufferSize);
					isb->dwBufferLength = currentInputLength;				    
					waveInAddBuffer(hWaveIn, isb, sizeof(WAVEHDR));
			    	propeller(IDC_PH_OUTPUT_LOST, ++outputPacketsLost);
					RecCount--;
					break;
			    }
			    if (!waNetNoMsgLoopIns) {
					DefaultMessageLoop();
				}
			    --RecCount;

				while (TRUE) {
					int firstTime = TRUE;
					
					/*	One little subtlety.  Since we allow the user to
						change compression modes on the fly, the buffer that
						just arrived may have been filled based on out of
						date compression modes.  In particular, if it's longer
						than we currently expect, it could frag the network.
						If this is the case, chop the extra samples off the
						end of the buffer.  This causes momentary loss of sound,
						but that's a lot better than the dreaded "Cannot write to
						socket" network error.  There's no problem processing a
						buffer shorter than expected, except a possible momentary
						click in GSM encoding. */
						
					isb = (LPWAVEHDR) lParam;
					if (isb->dwBytesRecorded > (DWORD) currentInputLength) {
						isb->dwBytesRecorded = currentInputLength;
					} else if (isb->dwBytesRecorded < (DWORD) currentInputLength &&
							   isb->dwBytesRecorded > 0) {
						memset(isb->lpData + isb->dwBytesRecorded,
							sampleAlignment == 1 ? 0x80 : 0,
							((int) (currentInputLength - isb->dwBytesRecorded)));
						isb->dwBytesRecorded = currentInputLength;
					}
					
					/*	Now iterate over all the MDI client windows.  For each
						which is an established connection, test whether the window
						is marked as accepting audio.  (If broadcast or
						conference mode is in effect, all windows are implicitly
						set to accepting audio.)  For each qualifying window, call
						shipSoundBuffer to send the audio to that connection.  */
							
				    while (hwndClient != NULL && isb->dwBytesRecorded > 0 && !inputPaused) {
				    	if ((WNDPROC) GetWindowLong(hwndClient, GWL_WNDPROC) == ((WNDPROC) connectWndProc)) {
					        LPCLIENT_DATA pClientData = CLIENTPTR(hwndClient);
						
					        if ((pClientData != NULL) &&
					            IS_CLIENT_WINDOW(hwndClient) &&
					            (pClientData->wantsInput || broadcasting || bConferencing)) {
								if (!bOverRun) {	// Overflow
									if (firstTime) {
    									isWaveSound = FALSE;
										createSoundBuffer(isb->lpData,
											(WORD) isb->dwBytesRecorded,
											audioChannels, samplesPerSecond,
											bytesPerSecond, (WORD) sampleAlignment);
										firstTime = FALSE;
									}
									shipSoundBuffer(hwndClient, pClientData);
								}
							}
						}
				        hwndClient = GetWindow(hwndClient, GW_HWNDNEXT);
				    }
				    if (inputTerm) {
						waveInUnprepareHeader(hWaveIn, isb, sizeof(WAVEHDR));
						if (isb->lpData != NULL) {
							GlobalFreePtr(isb->lpData);
						}
						GlobalFreePtr(isb);
						if (--waveHeadersAllocated == 0) {
							waveInClose(hWaveIn);
							hWaveIn = NULL;
							inputActive = FALSE;
						    propUpdateAudio();
						}
				    } else {
						Assert(currentInputLength <= inputSoundBufferSize);
						isb->dwBufferLength = currentInputLength;				    
						waveInAddBuffer(hWaveIn, isb, sizeof(WAVEHDR));
					}
					if (qsb == 0) {
						break;
					}
					lParam = qsb;
					qsb = 0;
				}
			}
			break;
        
        //	Output sound buffer complete
        
        case MM_WOM_DONE:
			{
				LPWAVEHDR waveHdr = (LPWAVEHDR) lParam;
				char **specsamp = (char **) (lParam + sizeof(WAVEHDR));
				int bufferMicroseconds;

				outputPending--;

				memcpy(&bufferMicroseconds, waveHdr->lpData + waveHdr->dwBufferLength, sizeof bufferMicroseconds);
				microsecondsPending -= bufferMicroseconds;
				if (microsecondsPending > 0) {
					smoothedMicrosecondsPending = smoothedMicrosecondsPending + (microsecondsPending - smoothedMicrosecondsPending) * 0.1;
				} else {
					smoothedMicrosecondsPending = 0;
				}
cPrintf("- time pending = %.2g", smoothedMicrosecondsPending / 1000000.0);

				/*	Unless disabled by the workaround waAudioNoOutputRateAdjustment,
					compare the smoothed estimate of the time it will take to clear the
					output queue to the chosen jitter interval.  If it's greater than
					the jitter interval by a prescribed amount, adjust the output sample
					rate to speed up the output rate of subsequently arriving samples.  */

				if (!waAudioNoOutputRateAdjustment) {
					int excessQueue = (int) ((smoothedMicrosecondsPending / 1000.0) - min(jitterBuf, AORA_MINIMUM_DELAY));

					if (excessQueue > AORA_ACTION_DELAY) {
						currentOutputRate = EXCHANGE_SAMPLE_RATE -
							((EXCHANGE_SAMPLE_RATE * AORA_MAXIMUM_RATE_PERCENT *
								min(excessQueue - AORA_ACTION_DELAY, AORA_MAXIMUM_RATE_DELAY)) / (AORA_MAXIMUM_RATE_DELAY * 100));
					} else {
						currentOutputRate = EXCHANGE_SAMPLE_RATE;
					}
					cPrintf("- Excess queue length: %d msec.  Rate = %d", excessQueue, currentOutputRate);
				}

				//	Update the queue length and time in queue in the extended status dialogue
				if (hDlgPropeller != NULL) {
					char s[80];
					
					if (outputPending == 0) {
						wsprintf(s, Format(6));
					} else {
						sprintf(s, Format(7), outputPending, microsecondsPending / 1000000.0);
					}
					SetDlgItemText(hDlgPropeller, IDC_PH_AUDIO_OUT_QUEUE, s);
				}

				waveOutUnprepareHeader(hWaveOut, waveHdr, sizeof(WAVEHDR));
				GlobalFreePtr(waveHdr->lpData);
				if (*specsamp != NULL) {
					spectrumUpdate((*specsamp) + sizeof(WORD), *((WORD *) (*specsamp)), 1, 8000, 8000, 1, TRUE);
					free(*specsamp);
				}
				GlobalFreePtr(waveHdr);
				if ((halfDuplexTransition || outputInShutdown) &&
						(outputPending == 0)) {
					waveOutClose(hWaveOut);
					outputActive = FALSE;
					if ((!outputInShutdown && halfDuplexTransition) || inputPaused) {
						V startWaveInput(hwnd);
						inputPaused = FALSE;
					}
					halfDuplexTransition = outputInShutdown = FALSE;
			    	propUpdateAudio();
#ifdef KILL_INPUT
				} else if ((outputPending == 0) && inputPaused) {
					answerSync();
					waveOutShutdown();
					if (!outputActive && inputPaused) {
						V startWaveInput(hwnd);
						inputPaused = FALSE;
					}
#endif
				}
			}						 
        	break;

		//	Text chat line to be sent to all connections

		case WM_CHAT_TEXT_SEND:
			{
			    HWND hConn;
			
				if (hwndMDIClient != NULL) {
					hConn = GetWindow(hwndMDIClient, GW_CHILD);
					while (hConn != NULL) {
			    		if ((WNDPROC) GetWindowLong(hConn, GWL_WNDPROC) ==
			    									((WNDPROC) connectWndProc)) {
			    			SendMessage(hConn, WM_CHAT_TEXT_SEND, 0, lParam);
			        		hConn = GetWindow(hConn, GW_HWNDNEXT);
						}
					}
				}
			}
			GlobalFree((LPSTR) lParam);
			break;

    default:
        
        	//	Check for help button in a file open/save dialogue we own
        
        	if (nMessage == fileOpenHelpButton && fileHelpKey != NULL) {
            	displayHelpTopicString(fileHelpKey);
        	}
        	break;	  
    }

    return DefFrameProc(hwnd, hwndMDIClient, nMessage, wParam, lParam);
}

