/*

	Window procedure for connection MDI window
	
*/

#include "netfone.h"

#define ASYNC_OUTPUT
                            
static struct {
	char header[4];
	unsigned short len, ilen;
	soundbuf sbm;
} mb = {{1, 2, 3, 4}};
#define sb	mb.sbm

soundbuf ebuf;				  		  // Utility sound buffer
static LONG pktlen;					  // Packet length to transmit
static struct adpcm_state adpcm;	  // ADPCM compression state
static int squelched = FALSE;		  // Squelched by VOX ?
static int sqpacket = FALSE;		  // Is this packet squelched
static char ourSendingHost[16];		  // Host name for sound buffer sendinghost
static int xseq = 0;				  // Robust mode transmit sequence number

char blankit[] = "                                                              "
				 "                                                              "
				 "                                                              ";

/*  GSMCOMP  --  Compress the contents of a sound buffer using GSM.  */

static void gsmcomp(soundbuf *asb)
{
    gsm_signal src[160];
    gsm_frame dst;
    int i, j, l = 0;
    char *dp = (asb->buffer.buffer_val) + sizeof(short);
    long ldata = asb->buffer.buffer_len; 
    
    for (i = 0; i < ldata; i += 160) {
        for (j = 0; j < 160; j++) {
            if ((i + j) < asb->buffer.buffer_len) {
                src[j] = audio_u2s(asb->buffer.buffer_val[i + j]);
            } else {
                src[j] = 0;
            }
        }
        gsm_encode(gsmh, src, dst);
        memcpy(dp, dst, sizeof dst);
        dp += sizeof dst;
        l += sizeof dst;
    }

    /* Hide original uncompressed buffer length in first 2 bytes of buffer. */
    
    *((short *) asb->buffer.buffer_val) = (short) ldata;
    revshort((short *) asb->buffer.buffer_val);
    asb->buffer.buffer_len = l + sizeof(short);
}

/*	ADPCMCOMP  --  Compress the contents of a sound buffer using ADPCM.  */

static void adpcmcomp(soundbuf *asb)
{
	unsigned char *dp = (unsigned char *) asb->buffer.buffer_val;
	struct adpcm_state istate;

	istate = adpcm;
	adpcm_coder_u(dp, (char *) dp, (int) asb->buffer.buffer_len, &adpcm);
	asb->buffer.buffer_len /= 2;

	/* Hide the ADPCM encoder state at the end of this buffer.
	   The shifting and anding makes the code byte-order
	   insensitive. */

	dp += asb->buffer.buffer_len;
	*dp++ = ((unsigned int) istate.valprev) >> 8;
	*dp++ = istate.valprev & 0xFF;
	*dp = istate.index;
	asb->buffer.buffer_len += 3;
}

/*	LPCCOMP  --  Compress the contents of a sound buffer using LPC.  */

static void lpccomp(soundbuf *asb)
{
	int i, l = 0;
	char *dp = ((char *) asb->buffer.buffer_val) + sizeof(short);
	unsigned char *src = ((unsigned char *) asb->buffer.buffer_val);
	lpcparams_t lp;

	asb->compression |= fCompLPC;
	for (i = 0; i < asb->buffer.buffer_len; i += LPC_FRAME_SIZE) {
		lpc_analyze(src + i, &lp);
		memcpy(dp, &lp, sizeof lp);
		dp += sizeof lp;
		l += sizeof lp;
	}

	// Hide original uncompressed buffer length in first 2 bytes of buffer.

	*((short *) asb->buffer.buffer_val) = (short) asb->buffer.buffer_len;
	revshort((short *) asb->buffer.buffer_val);
	asb->buffer.buffer_len = l + sizeof(short);
}

/*	LPC10COMP  --  Compress the contents of a sound buffer using LPC-10.  */

static void lpc10comp(soundbuf *asb)
{
	char *dp = (char *) asb->buffer.buffer_val;
	unsigned char *src = ((unsigned char *) asb->buffer.buffer_val);

	asb->compression |= fCompLPC10;
	asb->buffer.buffer_len = lpc10encode(src, dp, asb->buffer.buffer_len);
}

/*	LPC10STUFF	--	Stuff last 16 bytes of LPC10 packet in sendinghost. */

static int lpc10stuff(struct soundbuf *asb, int pktlen)
{
	if ((asb->compression & fCompLPC10) && (asb->buffer.buffer_len > 16)) {
		memcpy(asb->sendinghost, 
			((char *) asb) + (pktlen - (sizeof asb->sendinghost)),
			sizeof asb->sendinghost);
		pktlen -= sizeof asb->sendinghost;
	}
	return pktlen;
}

/*	LPC10UNSTUFF  --  Reverse the effect of lpc10stuff. */

static void lpc10unstuff(struct soundbuf *asb)
{
	if ((asb->compression & fCompLPC10) && (asb->buffer.buffer_len > 16)) {
		pktlen += sizeof asb->sendinghost;
	}
}

/*	CELPCOMP  --  Compress the contents of a sound buffer using CELP.  */

static void celpcomp(struct soundbuf *asb)
{
	short src[240];
	char celp_frame[18];
	int i, j, l = 0;
	char *dp = ((char *) asb->buffer.buffer_val) + sizeof(short);

	asb->compression |= fCompCELP;
	for (i = 0; i < asb->buffer.buffer_len; i += 240) {
		for (j = 0; j < 240; j++) {
			if ((i + j) < asb->buffer.buffer_len) {
				src[j] = audio_u2s(asb->buffer.buffer_val[i + j]);
			} else {
				src[j] = 0;
			}
		}
#ifdef CELP_USE_CONTEXT
		/* Allocate and initialise the CELP transmit encoding context the first time
		   we need to encode audio. */
		if (celp_xp == NULL) {
			celp_xp = (struct celp_context *) GlobalAllocPtr(GPTR, sizeof(struct celp_context));
			if (celp_xp == NULL) {
				return;
			}
			celp_context_init(celp_xp);
			celp_init(celp_xp, FALSE);
		}
		celp_encode(celp_xp, src, celp_frame);
#else
		celp_encode(src, celp_frame);
#endif
		memcpy(dp, celp_frame, sizeof celp_frame);
		dp += sizeof celp_frame;
		l += sizeof celp_frame;
	}

	/* Hide original uncompressed buffer length in first 2 bytes of buffer. */
	
	*((short *) asb->buffer.buffer_val) = htons((short) asb->buffer.buffer_len);
	asb->buffer.buffer_len = l + sizeof(short);
}

/*	CELPSTUFF	--	Stuff last 16 bytes of CELP packet in sendinghost. */

static int celpstuff(struct soundbuf *asb, int pktlen)
{
	if ((asb->compression & fCompCELP) && (asb->buffer.buffer_len > 16)) {
		memcpy(asb->sendinghost, ((char *) asb) + (pktlen - (sizeof asb->sendinghost)),
			sizeof asb->sendinghost);
		pktlen -= sizeof asb->sendinghost;
	}
	return pktlen;
}

/*	CELPUNSTUFF  --  Reverse the effect of celpstuff. */

static void celpunstuff(struct soundbuf *asb)
{
	if ((asb->compression & fCompCELP) && (asb->buffer.buffer_len > 16)) {
		pktlen += sizeof asb->sendinghost;
	}
}

/*  COMPRESS2X  --  Compress a sound buffer with Simple (discard every
					other sample) compression.  If you're doing both
					Simple and GSM compression, Simple compression must be
					done first.  */
					
void compress2X(soundbuf *asb)
{
#ifdef OLD2X
    LONG i;
	         
    asb->buffer.buffer_len /= 2;
    for (i = 1; i < asb->buffer.buffer_len; i++) {
        asb->buffer.buffer_val[i] = asb->buffer.buffer_val[i * 2];
    }
#else
	int li = (int) asb->buffer.buffer_len;
	int lo = li / 2;
	
	rate_flow(&xrate, (unsigned char *) asb->buffer.buffer_val,
			  (unsigned char *) asb->buffer.buffer_val,
			  &li, &lo);
	asb->buffer.buffer_len = lo; 
#endif
}

/*	AES_cbc_encrypt  --  Encrypt buffer with AES.  Cipher block
						 chaining is done within each buffer.  */
						 
void AES_cbc_encrypt(unsigned char *in,
					 unsigned char *out,
					 int len, aes_ctx *ctx)
{
	int i, j;
	unsigned char feedback[AES_BLOCK_SIZE];
	
	//	Initially zero the feedback buffer.
	memset(feedback, 0, AES_BLOCK_SIZE);
	Assert((len % AES_BLOCK_SIZE) == 0);		// Attempt to encrypt non-multiple of block length
	
	//	Loop over encryption blocks in the buffer.
	for (i = 0; i < len; i += AES_BLOCK_SIZE) {
		/*	XOR the next block to be encrypted with the
			last encrypted block.  */
		for (j = 0; j < AES_BLOCK_SIZE; j++) {
			in[j] ^= feedback[j];
		}
		//	Encrypt the block.
		aes_enc_blk(in, out, ctx);
		//	Save encrypted block in feedback to XOR with next.
		memcpy(feedback, out, AES_BLOCK_SIZE);
		//	Advance input and output pointers to next block.
		in += AES_BLOCK_SIZE;
		out += AES_BLOCK_SIZE;
	}
}

/*	MONITORPORT  --  Obtain an auxiliary socket to receive input
					 from a given port.  Existing sockets are reused
					 and their reference count incremented.  */

static struct auxSocket *monitorPort(unsigned short port)
{
	struct auxSocket *as = asList;
	WORD serr = 0;

	while (as != NULL) {
		if (port == as->asport) {
			break;
		}
		as = as->asnext;
	}
	
	if (as == NULL) {
#ifdef TRACE_AUX_SOCKET
OutputDebugString("Allocating aux socket.\r\n");
#endif	
		as = (struct auxSocket *) GlobalAllocPtr(GPTR, sizeof(struct auxSocket));
		if (as != NULL) {
			as->asnext = asList;
			asList = as;
			as->asrefc = 0;
			as->asport = port;
			as->asdata = as->asctrl = INVALID_SOCKET;
		}
	}
	if (as != NULL) {
		if (as->asrefc == 0) {
		    serr = CreateSocket(&(as->asdata), SOCK_DGRAM,
		    		htonl(INADDR_ANY), htons(as->asport));
			if (serr != 0) {
		        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(73),
		                port, serr, SockerrToString(serr));
		        goto bang;
			}
		    serr = CreateSocket(&(as->asctrl), SOCK_DGRAM,
		    		htonl(INADDR_ANY), htons((short) (as->asport + 1)));
			if (serr != 0) {
		        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(73),
		                port + 1, serr, SockerrToString(serr));
		        goto bang;
			}
#ifdef ASYNC_OUTPUT			
		    if (WSAAsyncSelect(as->asdata, hwndMDIFrame, WM_SOCKET_SELECT, FD_READ) != 0) {
		        serr = WSAGetLastError();
		    }
			if (serr != 0) {
		        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(73),
		                port, serr, SockerrToString(serr));
		        goto bang;
			}
		    if (WSAAsyncSelect(as->asctrl, hwndMDIFrame, WM_SOCKET_CONTROL, FD_READ) != 0) {
		        serr = WSAGetLastError();
		    }
			if (serr != 0) {
		        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(73),
		                port + 1, serr, SockerrToString(serr));
		        goto bang;
			}
#endif			
#ifdef TRACE_AUX_SOCKET
OutputDebugString("Opened aux socket.\r\n");
#endif
#ifdef IP_MAX_MEMBERSHIPS			
			multicastJoin(hwndMDIFrame, FALSE);
			multicastJoin(hwndMDIFrame, TRUE);
#endif						
		}
		as->asrefc++;
	}
	return as;
	
bang:
	if (as->asdata != INVALID_SOCKET) {
		closesocket(as->asdata);
		as->asdata = INVALID_SOCKET;
	}
	if (as->asctrl != INVALID_SOCKET) {
		closesocket(as->asctrl);
		as->asctrl = INVALID_SOCKET;
	}
	return NULL;
}

//	WRITEOUTPUT  --  Transmit output buffer to destination

static int writeOutput(LPCLIENT_DATA d, LPSTR buf, int buflen)
{
#ifdef MODEM
	if (d->modemConnection) {
		if (modemHandle != -1) {
			unsigned short bcrc;
			COMSTAT cs;
			int wrl, err;
		
			mb.len = (unsigned short) buflen;
			mb.ilen = ~mb.len;
			revshort(&mb.len);
			revshort(&mb.ilen);
			bcrc = crc((LPSTR) &mb, buflen + 4 + 2 * sizeof(unsigned short));
			revshort(&bcrc);
			err = GetCommError(modemHandle, &cs);
			if (err != 0) { 
				MsgBox(hwndMDIFrame, MB_ICONSTOP | MB_OK, Format(20), err);
				errorRant(hwndMDIFrame);
				return -1;
			} 
			wrl = WriteComm(modemHandle, &mb, buflen + 4 + 2 * sizeof(unsigned short));
			err = GetCommError(modemHandle, &cs);
			if (err != 0) { 
				MsgBox(hwndMDIFrame, MB_ICONSTOP | MB_OK, Format(21), err);
				errorRant(hwndMDIFrame);
				return -1;
			} 
			if (wrl > 0) {
				wrl = WriteComm(modemHandle, &bcrc, sizeof(short));
			}
			err = GetCommError(modemHandle, &cs);
			if (err != 0) { 
				MsgBox(hwndMDIFrame, MB_ICONSTOP | MB_OK, Format(22), err);
				errorRant(hwndMDIFrame);
				return -1;
			}
			return buflen;
		}
	} else
#endif	
	if (d->outputSocketBusy) {
		propeller(IDC_PH_OUTPUT_LOST, ++outputPacketsLost);
#ifdef OVERLOAD			
OutputDebugString("Socket busy--packet discarded.\r\n");
#endif
		return buflen;					
	} else {
		int stat, wasSentTo = FALSE;
		
#ifdef OVERLOAD
	int OL;
	for (OL = 0; OL < OVERLOAD; OL++) {
#endif			
		if (d->localLoopback || ((!useSendNotSendto || waNetNoConnect) &&
									(!waNetUseSend))) {
			if (d->localLoopback) {
				stat = loop_sendto(d, buf, buflen,
						(LPSOCKADDR) &(d->name), sizeof d->name);
			} else {									
				stat = sendto(d->sReply, buf, buflen, 0,
						(LPSOCKADDR) &(d->name), sizeof d->name);
			}
			if (stat < 0) {
				if (!waNetNoConnect) {
					useSendNotSendto = TRUE;
					if (hDlgPropeller != NULL) {
						SetDlgItemText(hDlgPropeller, IDC_PH_SENDTO, rstring(IDS_T_SEND));
					}
				}
			} else {
				wasSentTo = TRUE;
			}
		}
		/*	Careful!  Don't "optimise" this to "else if"; we have to be
			able to switch-hit when the first sendto() fails above. */
		if (!wasSentTo) {
			stat = send(d->sReply, buf, buflen, 0);
		}
		if (stat == buflen) {
			propeller(IDC_PH_PACKETS_SENT, ++packetsSent);
#ifdef OVERLOAD			
if ((rand() & 0x3F) == 0) { d->outputSocketBusy = TRUE; OutputDebugString("Dooooh!\r\n"); }  
#endif			
		} else {
			propeller(IDC_PH_OUTPUT_LOST, ++outputPacketsLost);

			if (!waNetNoOutOverflow) {

				/* Great leap of faith.  Treat a "WSAEWOULDBLOCK" error as
				   a truncated buffer and do nothing other than mark the
				   socket busy. */
				   
				d->outputSocketBusy = TRUE;
				if (stat == SOCKET_ERROR &&
					WSAGetLastError() == WSAEWOULDBLOCK) {
					stat = 0;
				}
			} 
#ifdef OVERLOAD			
OutputDebugString("Socket write failed.\r\n");
#endif
		}
#ifdef OVERLOAD
	}
#endif				
		return stat;
	}
}

//	SOCKETERRORBOX  --  Show message box for socket error

static void socketerrorbox(HWND hwnd, LPCLIENT_DATA d)
{
	if (d->modemConnection) {
	    MessageBox(hwnd, rstring(IDS_T_MODEM_WRITE_ERR), NULL,
	    	MB_ICONEXCLAMATION | MB_OK);
	} else {
	    MessageBox(hwnd, SockerrToString(WSAGetLastError()), rstring(IDS_T_SOCKET_WRITE_ERR),
	    	MB_ICONEXCLAMATION | MB_OK);
    }
} 

/*  SENDPKT  --  Send a message to the destination.  */

static int sendpkt(HWND hwnd, LPCLIENT_DATA d, struct soundbuf *asb)
{
    LONG lts = asb->buffer.buffer_len;
    LONG pkl = pktlen;
    struct soundbuf *osb = asb;

#ifdef CRYPTO
    if ((d->deskey[0] || d->ideakey[0] || d->opgpkey[0] ||
		d->blowfish_spec || d->aes_spec || d->otpFileName[0])) {
        int i;
        LONG slen;

        memcpy(&ebuf, asb, (int) pktlen);
        slen = lts;
        osb = &ebuf;

        /* DES encryption. */

        if (d->deskey[0]) {
			if (protocolSent == PROTOCOL_RTP || protocolSent == PROTOCOL_VAT) {
				LONG vlen = pktlen;
				des_key_schedule sched;
				des_cblock ivec;

                /* If we're DES encrypting we must round the size of
				   the data to be sent to be a multiple of 8 so that
				   the entire DES frame is sent.  If this is an RTP
				   packet, we may have to set the Pad bit in the
				   header and include a count of pad bytes at the end
				   of the packet. */

				memset(ivec, 0, 8);
				pkl = (pktlen + 7) & (~7);
				if (pkl > pktlen) {
					memset(((char *) &ebuf) + vlen, 0, (int) (pkl - vlen));
				}
				if ((protocolSent == PROTOCOL_RTP) && (pkl > vlen)) {
					char *p = (char *) &ebuf;

					p[0] |= 0x20; /* Set pad bytes present bit */
					p[pkl - 1] = (unsigned char) (pkl - vlen); /* Set pad count at end */
				}
				des_set_key((des_cblock *) (((protocolSent == PROTOCOL_RTP) ? d->rtpdeskey :
								d->vatdeskey) + 1), sched);
				des_ncbc_encrypt((des_cblock *) &ebuf,
					(des_cblock *) &ebuf, pkl, sched,
					(des_cblock *) ivec, DES_ENCRYPT);
				slen = pkl - (sizeof(struct soundbuf) - BUFL); 
			} else {
	        	char twibble[8];
	        	
	        	memcpy(twibble, d->deskey + 1, 8);
	            setkey(twibble);
	
	            /* If we're DES encrypting we must round the size of
	               the data to be sent to be a multiple of 8 so that
	               the entire DES frame is sent. */
	
	            slen = (slen + 7) & (~7);
	            for (i = 0; i < slen; i += 8) {
	
	                /* Apply cipher block chaining within the packet. */
	
	                if (i > 0) {
	                    int j;
	
	                    for (j = 0; j < 8; j++) {
	                        ebuf.buffer.buffer_val[(i + j)] ^=
	                            ebuf.buffer.buffer_val[(i + j) - 8];
	                    }
	                }
	                endes(ebuf.buffer.buffer_val + i);
	            }
	            ebuf.compression |= fEncDES;
	        }
        }

        /* IDEA encryption. */

        if ((protocolSent == PROTOCOL_SPEAKFREE) && d->ideakey[0]) {
            unsigned short iv[4];
	        char twibble[16];
	        
	        memcpy(twibble, d->ideakey + 1, 16);
            memset(iv, 0, sizeof(iv));
            initcfb_idea(iv, twibble, FALSE);

            /* If we're IDEA encrypting we must round the size of
               the data to be sent to be a multiple of 8 so that
               the entire IDEA frame is sent. */

            slen = (slen + 7) & (~7); 
            ideacfb(ebuf.buffer.buffer_val, (int) slen);
            close_idea();
            ebuf.compression |= fEncIDEA;
        }

        /* Blowfish encryption. */

        if ((protocolSent == PROTOCOL_SPEAKFREE) && d->blowfish_spec) {
            unsigned char iv[8];
	        
            memset(iv, 0, sizeof(iv));

            /* If we're Blowfish encrypting we must round the size of
               the data to be sent to be a multiple of 8 so that
               the entire Blowfish frame is sent. */

            slen = (slen + 7) & (~7); 
            BF_cbc_encrypt((unsigned char *) ebuf.buffer.buffer_val,
						   (unsigned char *) ebuf.buffer.buffer_val,
						   slen, &(d->blowfishkey), iv, BF_ENCRYPT);
            ebuf.compression |= fEncBF;
        }
        
        //	AES Encryption
        
		if ((protocolSent == PROTOCOL_SPEAKFREE)&& d->aes_spec) {
		
            /* If we're AES encrypting we must round the size of
				the data to be sent to be a multiple of 16 so that
				the entire AES block is sent. */

			slen = (slen + 15) & (~15);
			AES_cbc_encrypt((unsigned char *) ebuf.buffer.buffer_val,
							(unsigned char *) ebuf.buffer.buffer_val,
							slen, &(d->aesEkey));
			ebuf.compression |= fEncAES;
		}

        /* PGP session key encryption. */

        if ((protocolSent == PROTOCOL_SPEAKFREE) && d->opgpkey[0]) {
            unsigned short iv[4];
	        char twibble[16];
	        
	        memcpy(twibble, d->opgpkey + 1, 16);
            memset(iv, 0, sizeof(iv));
            initcfb_idea(iv, twibble, FALSE);

            /* If we're IDEA encrypting we must round the size of
               the data to be sent to be a multiple of 8 so that
               the entire IDEA frame is sent. */

            slen = (slen + 7) & (~7); 
            ideacfb(ebuf.buffer.buffer_val, (int) slen);
            close_idea();
            ebuf.compression |= fEncPGP;
        }

        /* One-time pad encryption. */

        if ((protocolSent == PROTOCOL_SPEAKFREE) && d->otpFileName[0]) {
            for (i = 0; i < slen; i++) {
                ebuf.buffer.buffer_val[i] ^= d->otp[i];
            }
            ebuf.compression |= fEncOTP;
        }
        pkl = slen + (sizeof(struct soundbuf) - BUFL);
    }
#endif
     
    {
    	int stat, nr;

		if (protocolSent == PROTOCOL_SPEAKFREE) {    	
			pkl = lpc10stuff(osb, pkl);
			pkl = celpstuff(osb, pkl);

            /*  If we're sending in Speak Freely protocol and robust transmission
				if enabled, include a packet sequence number, modulo 256, in the
				most significant 8 bits of the buffer_len field.  This is used
				on the receiving end to discard duplicates and re-order shuffled
				packets. The fCompRobust flag is set in the compression field to
				inform the receiver it should examine the sequence number.	*/
				
			if (robust > 1) {
				osb->compression |= fCompRobust;
				osb->buffer.buffer_len |= xseq << 24;
				xseq = (xseq + 1) & 0xFF;
			}
	        revlong(&osb->compression);
	        revlong(&osb->buffer.buffer_len);
	    }
		for (nr = 0; nr < ((protocolSent == PROTOCOL_SPEAKFREE) ? robust : 1); nr++) {
			stat = writeOutput(d, (LPSTR) osb, (int) pkl);
			if (stat < 0) {
				break;
			}
		}
        if (protocolSent == PROTOCOL_SPEAKFREE) {
	        revlong(&osb->compression);
	        revlong(&osb->buffer.buffer_len);
			if (osb->compression & fCompRobust) {
				osb->compression &= ~fCompRobust;
				osb->buffer.buffer_len &= 0xFFFFFFL;
			}
			celpunstuff(osb);
			lpc10unstuff(osb);
	    }
        if (stat < 0
&& WSAGetLastError() != WSAEINPROGRESS        ) {
            d->state = d->wantsInput ? SendingLiveAudio : Idle;
            d->wantsInput = FALSE;
            if (d->hFile != HFILE_ERROR) {
            	KillTimer(hwnd, 2);
	            _lclose(d->hFile);
	            d->hFile = HFILE_ERROR;
	        }
            socketerrorbox(hwnd, d);
            return FALSE;
        }
    }
    return TRUE;
}

/*	CHANGEAUDIOSTATE  --  Change transmitting / receiving state.  */

static void changeAudioState(HWND hwnd, LPCLIENT_DATA pClientData)
{
	if (pClientData->face_shown) {
		LPSTR hname = ((pClientData->uname != NULL) && (pClientData->uname[0]) &&
					   ((pClientData->uname[0] & 0xFF) != 0xFF)) ?
						pClientData->uname :
						(pClientData->localLoopback ? rstring(IDS_T_LOOPBACK) : 
						 (pClientData->modemConnection ?
		    			  rstring(IDS_T_MODEM_CONNECTION) : pClientData->szHost));
    	if (pClientData->wantsInput) {
    		char s[MAX_HOST + 20];
    		
    		strcpy(s, "==> ");
    		strcat(s, hname);
    		SetWindowText(hwnd, s); 
    	} else {
    		SetWindowText(hwnd, hname);
    	} 
	} else {
		InvalidateRect(hwnd, NULL, FALSE);
		UpdateWindow(hwnd);
	}
	
	/*	If "Beep on Transmit" is requested, beep when transitioning to
		transmit mode.  */
	if (waAudioBeepOnTransmit && (pClientData->wantsInput)) {
		MessageBeep(MB_ICONASTERISK);
	}
}

/*  SHIPSOUNDBUFFER  --  Output sound buffer to connection.  */

void shipSoundBuffer(HWND hwnd, LPCLIENT_DATA pClientData)
{
	if (sqpacket != squelched) {
		RECT wr;
		POINT cp;
	
		squelched = sqpacket;
		GetWindowRect(hwnd, &wr);
		GetCursorPos(&cp);
		if (PtInRect(&wr, cp)) {
	    	SetCursor((pClientData->wantsInput || broadcasting) ? 
	    				(squelched ? boltCursor : earCursor) :
	    				phoneCursor);
			UpdateWindow(hwnd);			// Change cursor to indicate squelch state
		}
	}

	if (squelched) {
		return;
	}
    
    if (protocolSent == PROTOCOL_SPEAKFREE) {
	    sb.compression = fProtocol | (pClientData->ring ? (fSetDest | fDestSpkr) : 0);
	    pClientData->ring = FALSE;
	    sb.compression |= pClientData->debugging ? fDebug : 0;
	    sb.compression |= pClientData->loopback ? fLoopBack : 0;
	    sb.compression |= compression ? fComp2X : 0;
	    sb.compression |= gsmcompress ? fCompGSM : 0;
	    sb.compression |= adpcmcompress ? fCompADPCM : 0;
	    sb.compression |= lpccompress ? fCompLPC : 0;
	    sb.compression |= lpc10compress ? fCompLPC10 : 0;
	    sb.compression |= celpcompress ? fCompCELP : 0;
	    /* Never offer face data to a multicast address.  It might be
	       nice, but the possibility of screw-ups is just too great
	       given the flakiness of multicast implementations. */
	    sb.compression |= ((faceFile != HFILE_ERROR) &&
	    	(!IN_MULTICAST(pClientData->inetSock.sin_addr.s_addr))) ? fFaceOffer : 0;
	    strcpy(sb.sendinghost, ourSendingHost);
	}
    sendpkt(hwnd, pClientData, &sb);
}

/*	SENDSESSIONCTRL  --  Send an RTP or VAT session control packet
						 on the control channel.  */
						 
static void sendSessionCtrl(LPCLIENT_DATA pClientData, char *msg, int msgl) 
{
	char *aux = (char *) &ebuf;
	int stat;

#ifdef CRYPTO			    			
	if ((!(protocolSent == PROTOCOL_SPEAKFREE)) && pClientData->rtpdeskey[0] &&
			!((protocolSent == PROTOCOL_RTP) && waProtNoRTCPCrypt)) {
		int vlen;
		des_key_schedule sched;
		des_cblock ivec;
						
		memset(ivec, 0, 8);
						
		if (protocolSent == PROTOCOL_RTP) {
						
			/* Encrypted RTCP messages are prefixed with 4 random
			   bytes to prevent known plaintext attacks. */
						
			memcpy(aux, &rtpdesrand, 4);
			memcpy(aux + 4, msg, msgl);
			msgl += 4;
		} else {
			memcpy(aux, msg, msgl);
		}
						
        /* If we're DES encrypting we must round the size of
		   the data to be sent to be a multiple of 8 so that
		   the entire DES frame is sent.  This applies only to
		   VAT, as the code that creates RTCP packets guarantees
           they're already padded to a multiple of 8 bytes. */
						
		vlen = msgl;
		msgl = (msgl + 7) & (~7);
		if (msgl > vlen) {
			memset(aux + vlen, 0, msgl - vlen);
		}
		des_set_key((des_cblock *) (((protocolSent == PROTOCOL_RTP) ?
						pClientData->rtpdeskey : pClientData->vatdeskey) + 1), sched);
		des_ncbc_encrypt((des_cblock *) aux,
			(des_cblock *) aux, msgl, sched,
			(des_cblock *) ivec, DES_ENCRYPT);
		msg = aux;
	}
#endif
	
	if (pClientData->localLoopback) {
		stat = loop_sendto(pClientData, msg, msgl,
				(LPSOCKADDR) &(pClientData->ctrl), sizeof pClientData->ctrl);
	} else {									
		if ((!useSendNotSendto || waNetNoConnect) && (!waNetUseSend)) {
			stat = sendto(pClientData->sControl, msg, msgl, 0,
					(LPSOCKADDR) &(pClientData->ctrl), sizeof pClientData->ctrl);
			if (stat < 0) {
				if (!waNetNoConnect) {
					useSendNotSendto = TRUE;
					if (hDlgPropeller != NULL) {
						SetDlgItemText(hDlgPropeller, IDC_PH_SENDTO, rstring(IDS_T_SEND));
					}
				}
			}
		}
		/*	Careful!  Don't "optimise" this to "else if"; we have to be
			able to switch-hit when the first sendto() fails above. */
		if (useSendNotSendto) {
			stat = send(pClientData->sControl, msg, msgl, 0);
		}
	}
	propeller(IDC_PH_PACKETS_SENT, ++packetsSent);
}						 

/*  CREATESOUNDBUFFER  --  Create a standard format sound buffer
						   with selected compression modes from a
						   set of raw samples received from the audio
						   input port.  Special gimmick: align==0 means
						   the data are already in place in mu-law
						   encoding (rate==8000 only).  This allows fast
						   processing of mu-law encoded sound files.  */
						
void createSoundBuffer(LPSTR buffer, WORD buflen, DWORD channels,
					   DWORD rate, DWORD bytesec, WORD align)
{
	int knownFormat = FALSE;

	//	If the spectrum display is open, let it see the raw samples
	spectrumUpdate(buffer, buflen, channels, rate, bytesec, align, FALSE);

	if (rate == 8000) {
		if (align == 2) {
			LONG i;
			int j;
			
			for (i = j = 0; i < (LONG) buflen / align; i++) {
				sb.buffer.buffer_val[j++] = audio_s2u((((WORD *) buffer)[i]));
			} 
		} else if (align == 1) {	// align == 1
			LONG i;
			int j;
			
			for (i = j = 0; i < (LONG) buflen; i++) {
				sb.buffer.buffer_val[j++] = audio_c2u((((BYTE *) buffer)[i]));
			} 
		} else  {	//	(align == 0) already stored in mu-law encoding
			align = 1;
		}
		sb.buffer.buffer_len = buflen / align;
		knownFormat = TRUE;
	} else if (rate == 11025 && align == 2) {
		LONG i;
		int j, k;
		
		for (i = j = k = 0; i < (LONG) (buflen / align); i++) {
			if ((k & 3) != 2  && ((i % 580) != 579)) {
				sb.buffer.buffer_val[j++] = audio_s2u((((WORD *) buffer)[i]));
			}
			k = (k + 1) % 11;
		} 
		sb.buffer.buffer_len = j;
		knownFormat = TRUE;
	} else if (rate == 11025 && align == 1) {
		LONG i;
		int j, k;
		
		for (i = j = k = 0; i < (LONG) (buflen / align); i++) {
			if ((k & 3) != 2  && ((i % 580) != 579)) {
				sb.buffer.buffer_val[j++] = audio_c2u((((BYTE *) buffer)[i]));
			}
			k = (k + 1) % 11;
		} 
		sb.buffer.buffer_len = j;
		knownFormat = TRUE;
	}
	
	sqpacket = FALSE;
	if (knownFormat) {
		if (voxmode != IDM_VOX_NONE) {
			LONG i;
			long alevel = 0;
			static long voxSampleCountdown;
			int j, thresh;
			
			thresh = (int) exp(log(32767.0) * ((1000 - noise_threshold) / 1000.0)); 
			for (i = 0, j = 0; i < sb.buffer.buffer_len; i++, j++) {
				int samp = audio_u2s(sb.buffer.buffer_val[j]);
				
				if (samp < 0) {
					samp = -samp;
				}
				alevel += samp;
			}
			alevel /= sb.buffer.buffer_len;
//{char s[132]; sprintf(s, "Nt = %d, Thresh = %d, Max = %d, Alevel = %ld, VU = %.2g\r\n",
//noise_threshold, thresh, maxsamp, alevel,
//log((double) alevel) / log(32767.0)); OutputDebugString(s);}			
			voxMonitorUpdate(alevel, 0);
			if (alevel < thresh) {
				if (voxSampleCountdown <= 0) {
					sqpacket = TRUE;
					sb.buffer.buffer_len = 0;
					return;
				}
				voxSampleCountdown -= sb.buffer.buffer_len;
			} else {
				switch (voxmode) {
					case IDM_VOX_FAST:
						voxSampleCountdown = 4000;
						break;
					case IDM_VOX_MEDIUM:
						voxSampleCountdown = 8000;
						break;
					case IDM_VOX_SLOW:
						voxSampleCountdown = 12000;
						break;
				}
			}			 	        
		}

	    if (compression) {
	    	compress2X(&sb);
	    }
    
	    if (gsmcompress) {
	        gsmcomp(&sb);
	    }

		if (adpcmcompress) {
// Sledgehammer to verify that lockup-prevention code works OK
//long l; l = GetTickCount();
//while (GetTickCount() - l < 350) ;
			adpcmcomp(&sb);
		}

		if (lpccompress) {
			lpccomp(&sb);
		}

		if (lpc10compress) {
			lpc10comp(&sb);
		}

		if (celpcompress) {
			celpcomp(&sb);
		}
        
        pktlen = sb.buffer.buffer_len + (sizeof(struct soundbuf) - BUFL);
        //	Ugly, ugly ugly.  RTP and VAT need compression modes here.
        sb.compression = fProtocol; 
	    sb.compression |= gsmcompress ? fCompGSM : 0;
	    sb.compression |= adpcmcompress ? fCompADPCM : 0;
	    sb.compression |= lpccompress ? fCompLPC : 0;
		if (protocolSent == PROTOCOL_RTP) {
			pktlen = rtpout(&sb, ssrc, timestamp, seq, spurt);
			seq++;
			timestamp += currentInputSamples;
		} else if (protocolSent == PROTOCOL_VAT) {
			pktlen = vatout(&sb, 0L, timestamp, spurt);
			timestamp += currentInputSamples;
		}
	
		spurt = FALSE;					  /* Not the start of a talk spurt */
	}
}											

/*  CREATENEWCONNECTION  --  Create a new connection MDI window.  */

HWND createNewConnection(LPCLIENT_DATA pClientData)
{
    MDICREATESTRUCT mcs;
    HWND hwnd;
    SOCKERR serr = 0;
    int vover;

#ifdef TRACE_FACE
	OutputDebugString("createNewConnection()\r\n");
#endif
	
	vover = (GetSystemMetrics(SM_CYFRAME) * 2) + GetSystemMetrics(SM_CYCAPTION) - 1;                    
    mcs.szClass = pszClientClass;
    mcs.szTitle = pClientData->localLoopback ?
			   	  rstring(IDS_T_LOOPBACK) : pClientData->szHost,
    mcs.hOwner = hInst;
    mcs.x = CW_USEDEFAULT;
    mcs.y = CW_USEDEFAULT;
    mcs.cx = tmAveCharWidth * 50;
    mcs.cy = ((4 * tmHeight) + 5) + vover;
    mcs.style = 0;

    hwnd = FORWARD_WM_MDICREATE(hwndMDIClient, (LPMDICREATESTRUCT) &mcs, SendMessage);

    if (hwnd == NULL) {
        return NULL;
    }
    
    pClientData->state = Idle;
    SetWindowLong(hwnd, GWL_CLIENT, (LONG) pClientData);
    if (!pClientData->modemConnection) {
	    serr = CreateSocket(&(pClientData->sReply), SOCK_DGRAM,
	                         htonl(INADDR_ANY), 0);
	
	    if (serr == 0) {
#ifdef ASYNC_OUTPUT	    
		    if (WSAAsyncSelect(pClientData->sReply, hwnd, WM_SOCKET_SELECT, FD_WRITE) != 0) {
		        serr = WSAGetLastError();
		    }
#endif
			if (serr == 0) {
		        pClientData->name.sin_family = PF_INET;
		        pClientData->name.sin_addr = pClientData->inetSock.sin_addr;
		        pClientData->name.sin_port = htons(pClientData->port);
		        
		        if (!waNetNoConnect) {
			        if (connect(pClientData->sReply, (LPSOCKADDR) &(pClientData->name),
			        	 sizeof(pClientData->name)) != 0) {
			            serr = WSAGetLastError();
			        }
		        }
		    }
   	    }
	    if (serr != 0) {
	       MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(23),
	                (LPSTR) pClientData->szHost, serr, SockerrToString(serr));
	        return NULL;
	    }
	    
	    //	Create control socket for RTP and VAT protocol connections
	    
	    serr = CreateSocket(&(pClientData->sControl), SOCK_DGRAM,
	                         htonl(INADDR_ANY), 0);
	
	    if (serr == 0) {
#ifdef ASYNC_OUTPUT	    
		    if (WSAAsyncSelect(pClientData->sReply, hwnd, WM_SOCKET_CONTROL, FD_WRITE) != 0) {
		        serr = WSAGetLastError();
		    }
#endif
			if (serr == 0) {
		        pClientData->ctrl.sin_family = PF_INET;
		        pClientData->ctrl.sin_addr = pClientData->inetSock.sin_addr;
		        pClientData->ctrl.sin_port = htons((short) (pClientData->port + 1));
		        
		        if (!waNetNoConnect) {
			        if (connect(pClientData->sControl, (LPSOCKADDR) &(pClientData->ctrl),
			        	 sizeof(pClientData->ctrl)) != 0) {
			            serr = WSAGetLastError();
			        }
		        }
			}
   	    }
	    if (serr != 0) {
	       MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(67),
	                (LPSTR) pClientData->szHost, serr, SockerrToString(serr));
	        return NULL;
	    }
	    
	    /*	If this is a nonstandard port, create auxiliary sockets to
	    	receive input from that port.  */
	    	
	    if (pClientData->port != NETFONE_COMMAND_PORT) {
	    	pClientData->auxSock = monitorPort(pClientData->port);
	    }
    }

    pClientData->gsmh = gsm_create();
	pClientData->rseq = -1;				// Initialise receive sequence number
	rate_start(&pClientData->rateconv, 4000, 8000);	// Initialise 2X decompression
    
    /* Save the sending host for Speak Freely protocol packets. */
    
    {
    	char gh[MAX_HOST];
    	
    	gethostname(gh, sizeof gh);
    	if (strlen(gh) > ((sizeof ourSendingHost) - 1)) {
    		gh[(sizeof ourSendingHost) - 1] = 0;
    	}
    	strcpy(ourSendingHost, gh);
    	strcpy(sb.sendinghost, ourSendingHost);
    }

#ifdef MODEM    
    if (pClientData->modemConnection) {
    	SetWindowText(hwnd, rstring(IDS_T_MODEM_CONNECTION));
		modemSessions++;
    } else
#endif    
    if (pClientData->szHost[0] == '(' && !pClientData->localLoopback) {
    
    	/* I'm sure by now you're shocked and stunned to discover that
    	   some Winsock implementations, Sun PC-NFS 5.1, to name one,
    	   don't correctly implement the WSAAsyncGetHostByAddr function.
    	   Oh, you can make the call, and you even get back a valid host
    	   name.  But doing so plants a time bomb which will kill you
    	   (at least under the debug kernel) much, much later when you call
    	   WSACleanup() right before exiting the program.  At that time,
    	   depending on where the random pointer inside their so-called
    	   WSHELPER points, you get either two invalid global pointer
    	   errors or a fatal error due to an object usage count underflow in the
    	   (bogus) global block.  If the waNetSynchronousGetHostnameAction is
    	   set, we eschew the asynchronous request and make the user
    	   wait for a blocking gethostbyaddr() which has the merit, at
    	   least, of not blowing us away at program termination time. */  
    
		if (!waNetSynchronousGetHostnameAction) {
		    	   
	    	/* This is a temporary connection initiated from the remote site.
	    	   Schedule a lookup to obtain the full domain name of the host,
	    	   not just the hostname included in the sound packet. */
	    	   
		    pClientData->getNameTask = WSAAsyncGetHostByAddr(hwnd, WM_SOCKET_ASYNC,
		                                    (CHAR *) &pClientData->inetSock.sin_addr,
		                                    sizeof(pClientData->inetSock.sin_addr),
		                                    PF_INET, pClientData->hostBuffer,
		                                    sizeof(pClientData->hostBuffer));
			if (pClientData->getNameTask == NULL) {
				int serr = WSAGetLastError();
				MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(24),
				        (LPSTR) pClientData->szHost, serr, SockerrToString(serr));
			}
		} else{
			LPHOSTENT h = gethostbyaddr((CHAR *) &pClientData->inetSock.sin_addr,
		                                 sizeof(pClientData->inetSock.sin_addr),
		                                 PF_INET);
					
			if (h == NULL) {
#ifdef SHOW_GET_HOST_NAME_ERROR			
	            int serr = WSAGetLastError();
								            
				MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(27),
				        pClientData->szHost, serr,
				        SockerrToString(serr));
#endif				        
			} else {
				strcpy(pClientData->szHost, h->h_name);
			    SetWindowText(hwnd, pClientData->localLoopback ?
			    	rstring(IDS_T_LOOPBACK) : pClientData->szHost);
	    		changeAudioState(hwnd, pClientData);
			}
			pClientData->getNameTask = NULL;			                             
		}	
	}

    DragAcceptFiles(hwnd, TRUE);
    ShowWindow(hwnd, SW_SHOW);
    openConnections++;
	propUpdateAudio();
	updateTrayToolTipText();

    return hwnd;
}

//	CONNFETCHFACE  --  Begin retrieval of a face image.

void connFetchFace(HWND hwndClient, LPCLIENT_DATA pClientData)
{
//	pClientData->face_stat = FSabandoned;
//	if (pClientData->face_stat != -1) {
#ifdef TRACE_FACE
		OutputDebugString("connFetchFace()\r\n");
#endif
		pClientData->face_address = 0L;
		pClientData->face_timeout = 0;
		pClientData->face_retry = 0;
		pClientData->face_stat = FSreply;   	// Activate request from timeout
		SetTimer(hwndClient, 5, (UINT) FaceFetchInterval, NULL);
//	}
}

//	STARTSOUNDFILE  --  Begin playing a sound file.

VOID startSoundFile(HWND hwnd, LPSTR pszFile)
{
    LPCLIENT_DATA pClientData;
    HFILE hFile = HFILE_ERROR;
    char magic[4];

    pClientData = CLIENTPTR(hwnd);
    
    pClientData->quitSoundFile = FALSE;
    pClientData->hFile = HFILE_ERROR;
    if (pClientData->timeout > 0) {
    	pClientData->timeout = 0;
    }
    pClientData->cbSent = 0L;

    //  Try to open it

    hFile = _lopen(pszFile, OF_READ | OF_SHARE_DENY_WRITE);

    if (hFile == HFILE_ERROR) {
        //  Error opening file

        MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(25),
                pszFile, (LPSTR) pClientData->szHost);
        goto FatalError;
    }
    _lread(hFile, magic, sizeof(long));
    
    /* See if it's a chunky, wavy RIFF.  If so, delegate
       handling of the file to the multimedia I/O package. */
       
	if (memcmp(magic, "RIFF", 4) == 0) {
		_lclose(hFile);
		if (!readWaveInit(hwnd, pClientData, pszFile)) {
			return;
		}
	} else {
	
	    /* If the file has a Sun .au file header, skip it.
	       Note that we still blithely assume the file is
	       8-bit ISDN u-law encoded at 8000 samples per
	       second. */
	
	    if (memcmp(magic, ".snd", 4) == 0) {
	        long startpos;
	
	        _lread(hFile, &startpos, sizeof(long));
	        revlong(&startpos);
	        _llseek(hFile, startpos, 0);
	    } else {
	        _llseek(hFile, 0L, 0);
	    }
	    pClientData->hFile = hFile;
    }
    
    pClientData->state = Transferring;
    spurt = TRUE;						// Consider sound file a "talk spurt"
    if (pClientData->timeout > 0) {
    	pClientData->timeout = 0;
    }

#ifdef SHOW_STATE
    InvalidateRect(hwnd, NULL, TRUE);
    UpdateWindow(hwnd);
#endif    
    DragAcceptFiles(hwnd, FALSE);
    if (SetTimer(hwnd, 2, 200, NULL) == 0) {
        MsgBox(NULL, MB_ICONSTOP | MB_OK, Format(26));
    }
    return;

FatalError:
    if (hFile != HFILE_ERROR) {
        _lclose(hFile);
    }
}

/*  FILEDROPPED  --  Handle file dropped in connection window.  */

static VOID fileDropped(HWND hwnd, HDROP hdrop)
{
    LPCLIENT_DATA pClientData;
    
    pClientData = CLIENTPTR(hwnd);

    //  Retrieve the dropped file

    DragQueryFile(hdrop, 0, pClientData->szFile, sizeof(pClientData->szFile));
    DragFinish(hdrop);
    
    //	Start output
    
    startSoundFile(hwnd, pClientData->szFile); 
}

/*  STATETOSTRING  --  Convert state value to string.  */

static LPSTR stateToString(CLIENT_STATE state)
{
    LPSTR pszResult;

    switch (state) {
	    case Embryonic:
	        pszResult = rstring(IDS_T_INITIALISING);
	        break;
	
	    case Idle:
	        pszResult = rstring(IDS_T_IDLE);
	        break;
	
	    case SendingLiveAudio:
	        pszResult = rstring(IDS_T_SENDING_LIVE);
	        break;
	
	    case Transferring:
	        pszResult = rstring(IDS_T_SENDING_FILE);
	        break;
	
	    case PlayingReceivedAudio:
	        pszResult = rstring(IDS_T_PLAYING_AUDIO);
	        break;
	
	    default:
	        pszResult = rstring(IDS_T_UNKNOWN);
	        break;
    }
    return pszResult;
}

/*  CONNECT_WNDPROC  --  Connection main window procedure.  */

LRESULT CALLBACK connectWndProc(HWND hwnd, UINT nMessage, WPARAM wParam, LPARAM lParam)
{
    LPCLIENT_DATA pClientData;

    pClientData = CLIENTPTR(hwnd);
    
    switch (nMessage) {
        
    	case WM_CHAR:
    		if (wParam == ' ') {
    			if (pClientData != NULL) {
    				if (!pClientData->wantsInput) {
    					goto spacebarOn;
    				} else {
    					goto spacebarOff;
    				}
    			}	
    		}
    		return 0;
        
        case WM_CLOSE:
        	/*	Mark the window closed at the request of the user.
        		We set the "Lazarus timer" running which prevents
        		the connection being remotely re-opened until the
        		timer expires.  */
        	if (pClientData != NULL) {
				Lazarus = pClientData->inetSock.sin_addr.s_addr;
				LazarusLong = LazarusLength;
			}
		    FORWARD_WM_CLOSE(hwnd, DefMDIChildProc);
        	return 0;
        	
        case WM_CREATE:
#ifdef TRACE_FACE
			OutputDebugString("Connection WM_CREATE()\r\n");
#endif
        	SetFocus(hwnd);
        	break;
        
        case WM_DESTROY:
        	if (pClientData != NULL) {
        		SendMessage(hwnd, WM_CLEAN_UP_YOUR_ACT, 0, 0L);
			}        
        	return 0;
        
        case WM_CLEAN_UP_YOUR_ACT:
        	if (pClientData != NULL) {
    			if (pClientData->wantsInput && --listeners <= 0) {
		        	terminateWaveInput();
					inputPaused = FALSE;
		        	listeners = 0;
		        }
        		gsm_destroy(pClientData->gsmh);
        		if (pClientData->hFile != HFILE_ERROR ||
			    	pClientData->mmioHandle != NULL) {
	        		KillTimer(hwnd, 2);
	        		if (pClientData->hFile != HFILE_ERROR) {
	        			_lclose(pClientData->hFile);
	        		}
	    			readWaveTerm(pClientData);
	        	}
	        	
	        	if (pClientData->pgpFileName[0] != 0) {
	        		KillTimer(hwnd, 3);				// Kill timer for incomplete PGP poll
	        		// T'would be nice to clean up the temp files here as well.
	        	}
	        	
	        	if (pClientData->opgpFileName[0] != 0) {
	        		KillTimer(hwnd, 4);				// Kill timer for incomplete PGP poll
	        		// T'would be nice to clean up the temp files here as well.
	        	}
	        	
	        	if (pClientData->face_stat == FSrequest ||
	        		pClientData->face_stat == FSreply) {
	        		KillTimer(hwnd, 5);				// Kill timer for incomplete face image
	        	}
	        	if (pClientData->face_bmp != NULL) {
	        		GlobalFreePtr(pClientData->face_bmp);	// Release face bitmap
	        	}
	        	
	        	/*	If we're transmitting in RTP or VAT protocol, send a
	        		Bye/Done message to indicate the connection's been
	        		closed.  */
	        	
	        	if (pClientData->sControl != INVALID_SOCKET) {
	        		unsigned char byebye[128];
	        		int byel;
	        		
	        		pClientData->sendSDEStimer = TIMEOUT_RESEND_SDES;
	        		if (protocolSent == PROTOCOL_RTP || protocolSent == PROTOCOL_SPEAKFREE) {
	        			byel = rtp_make_bye(byebye, ssrc, rstring(IDS_T_RAISON_CLOSING), TRUE);
	        			if (protocolSent == PROTOCOL_SPEAKFREE) {
	        				byebye[0] = (unsigned char) ((byebye[0] & 0x3F) | (1 << 6));
	        			}
	        		} else {
	        			byel = makevatdone(byebye, 0L);
	        		}
	        		if (!((protocolSent == PROTOCOL_SPEAKFREE) && waProtNoHeartbeat)) {
	        			sendSessionCtrl(pClientData, byebye, byel);
	        		}
	        	}
			    if (pClientData->sReply != INVALID_SOCKET) {
			        ResetSocket(pClientData->sReply);
			        pClientData->sReply = INVALID_SOCKET;
			    }
			    if (pClientData->sControl != INVALID_SOCKET) {
				    LINGER linger;
				
				    /*  Enable linger with a timeout of one second.  This 
				  		should, in theory, allow the BYE message to go out
				  		before the socket is closed.  Why not use the
				  		"graceful disconnect" mechanism?  Because there are
				  		WINSOCKs out there that don't do it right.  */
				
				    linger.l_onoff  = TRUE;
				    linger.l_linger = 1;
				
				    setsockopt(pClientData->sControl, SOL_SOCKET, SO_LINGER, (CHAR *) &linger, sizeof(linger));
				    closesocket(pClientData->sControl);
			        pClientData->sControl = INVALID_SOCKET;
			    }

				/*	If we're listening on a nonstandard channel, decrement
					the reference count on the auxiliary socket pair and
					close if it's zero.  */
					
				if (pClientData->auxSock != NULL) {
					if (--pClientData->auxSock->asrefc == 0) {
#ifdef TRACE_AUX_SOCKET
OutputDebugString("Closing aux socket.\r\n");
#endif					
				        ResetSocket(pClientData->auxSock->asdata);
				        pClientData->auxSock->asdata = INVALID_SOCKET;
				        ResetSocket(pClientData->auxSock->asctrl);
				        pClientData->auxSock->asctrl = INVALID_SOCKET;
					}
				}
			
			    if (pClientData->hFile != HFILE_ERROR) {
			        _lclose(pClientData->hFile);
			        pClientData->hFile = HFILE_ERROR;
			    }
			
#ifdef MODEM
	        	if (pClientData->modemConnection) {
	        		modemSessions--;
	        	}
#endif	        	
	        	
	        	if (pClientData->getNameTask != NULL) {
	        		WSACancelAsyncRequest(pClientData->getNameTask);
	        		pClientData->getNameTask = NULL;	
	        	}
	        	
				if (pClientData->uname != NULL) {
					GlobalFreePtr(pClientData->uname);
					pClientData->uname = NULL;
				}

				if (pClientData->d_celp_ctx != NULL) {	// Release CELP decoding context if allocated
					GlobalFreePtr(pClientData->d_celp_ctx);
					pClientData->d_celp_ctx = NULL;
				}

        		loop_flush(pClientData);				// Flush any queued local loopback data
	        	
        		GlobalFreePtr(pClientData);
        		SetWindowLong(hwnd, GWL_CLIENT, 0L);
			    openConnections--;
				propUpdateAudio();
				updateTrayToolTipText();
	        }
        	return 0;
        	
        case WM_DROPFILES:
        	if (pClientData != NULL && !broadcasting) {
				pClientData->timeout = -1;	// Send file immortalises connection
				fileDropped(hwnd, (HDROP) wParam);
        	}
        	break;

#ifdef GATES_OF_HELL

		/* Well, golly, I though it would be kinda nice to keep
		   the user from inflating the connection window larger than
		   the face image in it--doing so only wastes screen
		   real estate, after all.  Little did I know that by trying
		   to do so (at least with an MDI window), I stuck my toe
		   back into the tree chipper and caused all kinds of
		   things to let go--window size changing when iconised
		   and reactivated, the "disappearing minimise button
		   syndrome", etc. etc.  The Developer CD serves up the
		   usual crop of incoherent blithering.  I give up--go
		   ahead and make the bloody window as big as Siberia
		   if you like. */
        	
        case WM_GETMINMAXINFO:
    		if (pClientData != NULL && pClientData->face_shown) {
    			MINMAXINFO *mm = (MINMAXINFO *) lParam;
				BITMAPINFOHEADER *bmi;
			    RECT cr, wr;
            			
			    GetWindowRect(hwnd, &wr);
			    GetClientRect(hwnd, &cr);
	                        
	            bmi = (BITMAPINFOHEADER *) (pClientData->face_bmp + sizeof(BITMAPFILEHEADER));
    			
    			//	Don't allow resize of window larger than face

    			mm->ptMaxSize.x = mm->ptMaxTrackSize.x = ((int) bmi->biWidth) + ((wr.right - wr.left) - cr.right); 
    			mm->ptMaxSize.x = mm->ptMaxTrackSize.y = ((int) bmi->biHeight) + ((wr.bottom - wr.top) - cr.bottom); 
    		} 
        	break;
#endif        	
        	
        case WM_LBUTTONDBLCLK:
			if (pClientData != NULL && pClientData->wantsInput == TRUE) {
				pClientData->wantsInput = 2;
	        	if (pClientData->buttonUpTimer) {
	        		KillTimer(hwnd, 8);
	        		pClientData->buttonUpTimer = FALSE;
	        	}
				break;
			}        
        case WM_LBUTTONDOWN:
spacebarOn: if (pClientData != NULL && !pClientData->wantsInput && !broadcasting) {
	        	if (listeners == 0) {
					inputPaused = FALSE;
		        	if (!startWaveInput(hwnd)) {
		        		//	Couldn't turn on wave audio input
		        		break;
		        	}
		        }
	        	if (pClientData->buttonUpTimer) {
	        		KillTimer(hwnd, 8);
	        		pClientData->buttonUpTimer = FALSE;
	        	}
		        spurt = TRUE;				// Mark start of talk spurt
		        loop_flush(pClientData);	// Flush any unplayed local loop packets
	        	pClientData->wantsInput = (nMessage == WM_LBUTTONDBLCLK) ? 2 : TRUE;
		        listeners++;
		        pClientData->timeout = -1;	// Send audio immortalises connection
		        pClientData->state = SendingLiveAudio;
	        	SetCursor(earCursor);
	        	changeAudioState(hwnd, pClientData);
	        }
        	break;
        	
        case WM_LBUTTONUP:
        	if (pClientData != NULL && !broadcasting && !pClientData->buttonUpTimer) {
        		SetTimer(hwnd, 8, GetDoubleClickTime(), NULL);
        		pClientData->buttonUpTimer = TRUE;
        		break;
        	}
        
spacebarOff:if (pClientData != NULL && !broadcasting) {
	        	if (pClientData->wantsInput == TRUE) {
		        	pClientData->wantsInput = FALSE;
			        pClientData->state = pClientData->hFile != HFILE_ERROR ?
			        	Transferring : Idle;
		        	SetCursor(phoneCursor);
	        		changeAudioState(hwnd, pClientData);
	    			if (--listeners <= 0) {
			        	terminateWaveInput();
						inputPaused = FALSE;
			        	listeners = 0;
			        }

			    /* If this the button-up following a double click, don't
			       turn off listening.  This allows a double click to latch
			       input mode for a window. */
		        } else if (pClientData->wantsInput == 2) {
		        	pClientData->wantsInput = TRUE;
		        	if (pClientData->buttonUpTimer) {
		        		pClientData->buttonUpTimer = FALSE;
		        		KillTimer(hwnd, 8);
		        	}
		        	SetCursor(earCursor);
	    			UpdateWindow(hwnd);
		        }
	        } 
        	break;
        	
        case WM_MOUSEMOVE:
        	if (pClientData != NULL) {
	        	SetCursor((pClientData->wantsInput || broadcasting) ? 
	        				(squelched ? boltCursor : earCursor) :
	        				phoneCursor);
	        }
        	break;
        
        case WM_PAINT:
			{
#define DCOL	11			
			    PAINTSTRUCT psPaint;
			    HDC hdc;
			    HBITMAP ibmap = NULL;
			    int active = hwnd == ((HWND) LOWORD(SendMessage(hwndMDIClient, WM_MDIGETACTIVE, 0, 0L))),
			    	resized = FALSE;
                
			    hdc = BeginPaint(hwnd, &psPaint);
			    if (pClientData != NULL) {
			    	if (pClientData->face_stat == FScomplete &&
			    		pClientData->face_bmp != NULL) {
		    			int bx, by;
					    RECT cr, wr;
	            		BITMAPFILEHEADER *bfh;
	            		BITMAPINFOHEADER *bmi;
	            		char _huge *bits;
					    HPALETTE bpal = NULL, opal;
        				int i;
        				LPLOGPALETTE lp;
        				BITMAPINFO *bh;
        				unsigned short *palidx;
        				LPSTR lpalette;
        				unsigned short *savepal;
#ifdef TRACE_FACE
{	char s[256];
						
    wsprintf(s, "Paint %04X: %s\r\n", hwnd, pClientData->szHost);
	OutputDebugString(s);
}
#endif						
					    
					    pClientData->face_shown = FALSE;
					    savepal = (unsigned short *) GlobalAllocPtr(GPTR,
					    			sizeof(LOGPALETTE) +
					    			sizeof(PALETTEENTRY) * 256 +
					    			sizeof(short) * 256);
					    if (savepal == NULL) {
					    	goto face_failed;
					    }
					    lpalette = ((LPSTR) savepal) + 256 * sizeof(short); 
					    GetWindowRect(hwnd, &wr);
					    GetClientRect(hwnd, &cr);
                        
                        bfh = (BITMAPFILEHEADER *) pClientData->face_bmp;
                        bmi = (BITMAPINFOHEADER *) (pClientData->face_bmp + sizeof(BITMAPFILEHEADER));
                        bh = (BITMAPINFO *) bmi; 
            			bx = (int) bmi->biWidth;
            			by = (int) bmi->biHeight;
            			
            			if (bmi->biClrUsed == 0) {
            				bmi->biClrUsed = 1L << bmi->biBitCount;
            			}

	            		bits = (char *) (pClientData->face_bmp + bfh->bfOffBits);

        				lp = (LOGPALETTE *) lpalette;
        				palidx = (unsigned short *) bh->bmiColors;
        				memcpy(savepal, palidx, 256 * sizeof(short));
        				lp->palVersion = 0x0300;
        				lp->palNumEntries = (WORD) bmi->biClrUsed;
        				for (i = 0; i < ((int) bmi->biClrUsed); i++) {
        					lp->palPalEntry[i].peRed = bh->bmiColors[i].rgbRed;
        					lp->palPalEntry[i].peGreen = bh->bmiColors[i].rgbGreen;
        					lp->palPalEntry[i].peBlue = bh->bmiColors[i].rgbBlue;
        					lp->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
//        					if (active) {
//								palidx[i] = (unsigned short) i;
//							}
        				}
        				bpal = CreatePalette(lp);
        				if (active && bpal != NULL) {
							opal = SelectPalette(hdc, bpal, FALSE);
							RealizePalette(hdc);
        				}

	            		ibmap = CreateDIBitmap(hdc, bmi, CBM_INIT,
	            					bits, (BITMAPINFO *) bmi, DIB_RGB_COLORS);
        				memcpy(palidx, savepal, 256 * sizeof(short));
			            if (ibmap != NULL) {
			            	HDC hMemDC;
			            	HBITMAP obmap;
			            	HPALETTE mopal;
		
							hMemDC = CreateCompatibleDC(hdc);
							if (bpal != NULL) {
								mopal = SelectPalette(hMemDC, bpal, FALSE);
							}
							obmap = SelectObject(hMemDC, ibmap);
							if (bx <= cr.right && by <= cr.bottom) {
			            		BitBlt(hdc, (cr.right - bx) / 2, (cr.bottom - by) / 2,
			            			bx, by, hMemDC, 0, 0, SRCCOPY);
							} else {
								int nx, ny;
								double xshrink = ((double) bx) / cr.right,
									   yshrink = ((double) by) / cr.bottom;
		
#ifdef TRACE_FACE
								OutputDebugString("Yick!!!  Had to stretch face bitmap.\r\n");
#endif
								if (xshrink > yshrink) {
									nx = cr.right;
									ny = (int) (by / xshrink);
								} else {
									ny = cr.bottom;
									nx = (int) (bx / yshrink);
								}
								SetStretchBltMode(hdc, STRETCH_DELETESCANS);
								StretchBlt(hdc, (cr.right - nx) / 2, (cr.bottom - ny) / 2, nx, ny,
									hMemDC, 0, 0, bx, by, SRCCOPY);
							}
			            	SelectObject(hMemDC, obmap);
			            	DeleteObject(ibmap);
			            	if (bpal != NULL) {
			            		if (active) {
				            		SelectPalette(hdc, opal, FALSE);
				            	}
				            	SelectPalette(hMemDC, mopal, FALSE);
			            		DeleteObject(bpal);
			            	}
			            	DeleteDC(hMemDC);
			            	pClientData->face_shown = TRUE;
			            }
			            GlobalFreePtr(savepal);			    		
			    	}
face_failed:			    	
			    	if (!pClientData->face_shown) {
			    		int n = 0;
			    		RECT cr;
						HFONT ofont;
						char cryptoModes[256];
			    		
						ofont = SelectObject(hdc, GetStockObject(ANSI_VAR_FONT));
						SetBkColor(hdc, GetSysColor(COLOR_MENU));
						SetTextColor(hdc, GetSysColor(COLOR_MENUTEXT));
				        WinPrintf(hdc, n, 1, pClientData->modemConnection ?
				        	rstring(IDS_T_DIAL_STRING) : rstring(IDS_T_HOST));
				        WinPrintf(hdc, n++, DCOL, pClientData->szHost);
				        
				        if (pClientData->modemConnection) {
					        WinPrintf(hdc, n, 1, rstring(IDS_T_MODEM_CONNECTION_L));
				        } else {
					        WinPrintf(hdc, n, 1, rstring(IDS_T_ADDRESS));
					        WinPrintf(hdc, n++, DCOL, Format(48),
					        	inet_ntoa(pClientData->inetSock.sin_addr),
					        	pClientData->port);
	                    }
	                    
	                    //	Transmitting ?
	                    
	                    WinPrintf(hdc, n++, 1, pClientData->wantsInput ? rstring(IDS_T_TRANSMITTING) :
	                    	rstring(IDS_T_BLANKTRANSMIT));
	                    	
	                    //	Transmission protocol
	                    
	                    WinPrintf(hdc, n, 1, Format(71));
	                    WinPrintf(hdc, n++, DCOL, rstring(IDS_PROTOCOL_TYPES + pClientData->protocol));
	                    
	                    //	Encryption modes
	                    	                    
	                    cryptoModes[0] = 0;
#define showCrypto(m, s)	if ((m)) { if (cryptoModes[0] != 0) { strcat(cryptoModes, "+"); } strcat(cryptoModes, rstring(s)); }
						showCrypto(pClientData->aes_spec, IDS_CRYPTO_AES);
						showCrypto(pClientData->deskey[0], IDS_CRYPTO_DES);
						showCrypto(pClientData->ideakey[0], IDS_CRYPTO_IDEA);
						showCrypto(pClientData->opgpkey[0], pk_package == GPG ? IDS_PF_PUBLIC_KEY_GPG : IDS_CRYPTO_PGP);
						showCrypto(pClientData->blowfish_spec, IDS_CRYPTO_BLOWFISH);
						showCrypto(pClientData->otpFileName[0], IDS_CRYPTO_KEYFILE);
						if (cryptoModes[0] != 0) {
							WinPrintf(hdc, n, 1, Format(79));							
							WinPrintf(hdc, n++, DCOL, cryptoModes);
						}
												
	                    //	If in conference, names of users	
	                    if ((pClientData->uname != NULL) &&
	                    	((pClientData->uname[0] & 0xFF) == 0xFF)) {
	                    	LPSTR sp, np;
	                    	int nl = 0;
	                    	
	                    	WinPrintf(hdc, n, 1, Format(74));
	                    	sp = pClientData->uname + 1;
	                    	while (TRUE) {
	                    		np = strstr(sp, ", ");
	                    		if (np != NULL) {
	                    			*np = 0;
	                    		}
	                    		WinPrintf(hdc, n++, DCOL, "%s", sp);
	                    		if (np != NULL) {
	                    			*np = ',';
	                    			sp = np + 2;
	                    		} else {
	                    			break;
	                    		}
	                    		nl++;
/*	Too many in conference causes a crash I cannot reproduce
	Limit to 8 names shown for now.  */	                    		
if (nl >= 8) {
	WinPrintf(hdc, n++, DCOL, "...");
	break; 
}
	                    	}
	                    } else {	// Not in conference.  Details of single user
							//	User name
		                    if ((pClientData->uname != NULL) && (pClientData->uname[0])) {
						        WinPrintf(hdc, n, 1, Format(69));
						        WinPrintf(hdc, n++, DCOL, "%s", pClientData->uname);
		                    }
		                    
		                    //	User's E-mail address
		                    if (pClientData->email[0]) {
						        WinPrintf(hdc, n, 1, Format(70));
						        WinPrintf(hdc, n++, DCOL, "%s", pClientData->email);
		                    }
		                    
		                    //	User's client program name
							if (pClientData->szRemoteProgram[0]) {
									WinPrintf(hdc, n, 1, Format(77));
									WinPrintf(hdc, n++, DCOL, "%s", pClientData->szRemoteProgram);
							}						        
	                    }
	
#ifdef SHOW_STATE
						/* It's nice to show the state, but costly to update on
						   every packet. */
						   			
				        WinPrintf(hdc, n, 1, "State:");
				        WinPrintf(hdc, n++, DCOL, "%s", stateToString(pClientData->state));
				
				        switch (pClientData->state) {
				        	case Idle:
					            WinPrintf(hdc, n++, 1, blankit);
					            WinPrintf(hdc, n++, 1, blankit);
				        		break;
				        		
				        	case Transferring:
					            WinPrintf(hdc, n, 1, "File: ");
					            WinPrintf(hdc, n++, DCOL, "%s", pClientData->szFile);
					            WinPrintf(hdc, n, 1, "Bytes sent:");
					            WinPrintf(hdc, n++, DCOL, "%lu", pClientData->cbSent);
					            break;
					            
					        case PlayingReceivedAudio:
					            WinPrintf(hdc, n, 1, "Bytes received:");
					            WinPrintf(hdc, n++, DCOL, "%lu", pClientData->cbReceived);
								break;				        	
				        }
#endif

						SelectObject(hdc, ofont);
	                    GetClientRect(hwnd, &cr);
	                    if (cr.bottom != (n * tmHeight + 5)) {
	                    	RECT wr;
	                    	
	                    	GetWindowRect(hwnd, &wr);
							SetWindowPos(hwnd, HWND_TOP,
								0, 0, wr.right - wr.left, 
								((wr.bottom - wr.top) - cr.bottom) +
								(n * tmHeight + 5), SWP_NOMOVE | SWP_NOZORDER);
							resized = TRUE;
//OutputDebugString("Resized connection window.\r\n");								
	                    }
				    }
#undef DCOL
				}			    
			
			    EndPaint(hwnd, &psPaint);
			    if (resized) {
			    	/* Oops--we resized the window after finishing the paint.
			    	   Now we have to invalidate it again so it will be
			    	   repainted in the new size. */
			    	InvalidateRect(hwnd, NULL, TRUE);
			    	UpdateWindow(hwnd);
			    }			
			}
        	break;
        	
		case WM_MDIACTIVATE:
			if (wParam == FALSE || IsIconic(hwnd)) {
				break;
			}
	    	if (pClientData != NULL && pClientData->face_shown) {
			    HDC hdc;
        		BITMAPINFOHEADER *bmi;
			    HPALETTE bpal = NULL, opal;
				int i;
				LPLOGPALETTE lp;
				BITMAPINFO *bh;
				LPSTR lpalette;
#ifdef TRACE_FACE
{	char s[256];
						
    wsprintf(s, "MDI activate %04X: %s\r\n", hwnd, pClientData->szHost);
	OutputDebugString(s);
}
#endif						
					    
			    lpalette =  GlobalAllocPtr(GPTR, sizeof(LOGPALETTE) +
			    			sizeof(PALETTEENTRY) * 256);
			    if (lpalette != NULL) {
                    bmi = (BITMAPINFOHEADER *) (pClientData->face_bmp + sizeof(BITMAPFILEHEADER));
                    bh = (BITMAPINFO *) bmi; 
    				lp = (LOGPALETTE *) lpalette;
    				lp->palVersion = 0x0300;
    				lp->palNumEntries = (WORD) bmi->biClrUsed;
    				for (i = 0; i < ((int) bmi->biClrUsed); i++) {
    					lp->palPalEntry[i].peRed = bh->bmiColors[i].rgbRed;
    					lp->palPalEntry[i].peGreen = bh->bmiColors[i].rgbGreen;
    					lp->palPalEntry[i].peBlue = bh->bmiColors[i].rgbBlue;
    					lp->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
    				}
    				bpal = CreatePalette(lp);
    				if (bpal != NULL) {
			   			hdc = GetDC(hwnd);
						opal = SelectPalette(hdc, bpal, FALSE);
						i = RealizePalette(hdc);
						SelectPalette(hdc, opal, FALSE);
			            DeleteObject(bpal);						
    					ReleaseDC(hwnd, hdc);
    					if (i > 0) {
    						HWND oldwin;
    						
    						InvalidateRect(hwnd, NULL, TRUE);
    						UpdateWindow(hwnd);
							oldwin = (HWND) wParam;
    						if (oldwin != NULL) {
	    						InvalidateRect(oldwin, NULL, TRUE);
	    						UpdateWindow(oldwin);
    						}
    					}
    					return i;
    				}
    				GlobalFreePtr(lpalette);
    			}
    		}
        	break;

#ifdef ASYNC_OUTPUT        	
        case WM_SOCKET_SELECT:
        	if (pClientData != NULL) {
        		pClientData->outputSocketBusy = FALSE;
        	}
#ifdef OVERLOAD			
{ char s[80];
  wsprintf(s, "WM_SOCKET_SELECT Event = %d, Error = %d\r\n",
  	WSAGETSELECTEVENT(lParam), WSAGETSELECTERROR(lParam));
OutputDebugString(s);
}
#endif
		case WM_SOCKET_CONTROL:        	
        	return 0;
#endif        	
        	
        case WM_SOCKET_ASYNC:
        	if (pClientData != NULL) {
       			if (WSAGETASYNCERROR(lParam) == 0) {
				    LPHOSTENT host;
				
				    host = (LPHOSTENT) pClientData->hostBuffer;
				    SetWindowText(hwnd, pClientData->localLoopback ?
				    	rstring(IDS_T_LOOPBACK) : host->h_name);
				    strcpy(pClientData->szHost, host->h_name);
	        		changeAudioState(hwnd, pClientData);
#ifdef SHOW_GET_HOST_NAME_ERROR	        		
			    } else {
					MsgBox(hwnd, MB_ICONSTOP | MB_OK, Format(27),
					        pClientData->szHost, WSAGETASYNCERROR(lParam),
					        SockerrToString(WSAGETASYNCERROR(lParam)));
#endif					        
			    }
        	}
			pClientData->getNameTask = NULL;
        	break;

		/*	Send text chat message as an RTCP APP packet on the control
			channel.  This is available only if the user is sending in
			Speak Freely protocol.  */

		case WM_CHAT_TEXT_SEND:
			if (protocolSent == PROTOCOL_SPEAKFREE) {
				char v[256];

				LPSTR chat_text = (LPSTR) lParam;
				int l, n = strlen(chat_text);

				l = rtp_make_app(v, ssrc, TRUE, RTCP_APP_TEXT_CHAT, chat_text);
				// Set Speak Freely protocol flag in packet
				v[0] = (char) ((v[0] & 0x3F) | (1 << 6));
				sendSessionCtrl(pClientData, v, l);
			}
			break;
			        	
        case WM_TIMER:
        	{
    			DWORD startTicks = GetTickCount();
				DWORD dwElapsedTime = GetTickCount();

			    if (pClientData == NULL) {
			         break;
			    }

				/* If there are no buffers pending, advance the timeout
				   counter.  When it reaches TIMEOUT_CONNECTION, close the
				   connection. */
			
			    if (wParam == FRAME_TIMER_ID && !broadcasting &&
			    	pClientData->timeout >= 0 && outputPending == 0) {
					if ((pClientData->timeout++) >= TIMEOUT_CONNECTION) {
        				FORWARD_WM_MDIDESTROY(hwndMDIClient, hwnd, SendMessage);
						return 0;
			        }
			        if (pClientData->timeout == 5) {
						if (!IsIconic(hwnd)) {
							pClientData->cbReceived = 0;
							pClientData->state = Idle;
if (pClientData->face_stat == -1) {
	pClientData->face_stat = FSinit;	
}							
#ifdef SHOW_STATE							
			    			InvalidateRect(hwnd, NULL, TRUE);
			    			UpdateWindow(hwnd);
#endif 			
						}
			        }
			    }
			    
			    /*	If it's time to resend the session identity
			    	packet, go do it.  */
			    	
			    if (wParam == FRAME_TIMER_ID) {
		    		if (--(pClientData->sendSDEStimer) <= 0) {
		    			char *msg = NULL;
		    			int msgl;
		    			char pid;
			    			
		    			pClientData->sendSDEStimer = TIMEOUT_RESEND_SDES;
		    			switch (protocolSent) {
		    				case PROTOCOL_RTP:
		    				case PROTOCOL_SPEAKFREE:
		    					msg = rtpsdes;
		    					msgl = rtpsdesl;
		    					break;
			    					
		    				case PROTOCOL_VAT:
		    					msg = vatid;
		    					msgl = vatidl;
		    					break;
		    			}
                        if (msg != NULL) {
							pid = msg[0];
							if (protocolSent == PROTOCOL_SPEAKFREE) {
                        		msg[0] = (char) ((msg[0] & 0x3F) | (1 << 6));
							}
							if (!((protocolSent == PROTOCOL_SPEAKFREE) && waProtNoHeartbeat)) {
								sendSessionCtrl(pClientData, msg, msgl);
							}
							msg[0] = pid;
						}
			    	}
			    } 
			    
			    /* If a broadcast is underway and the site has requested
			       to unsubscribe, close the connection after a decent
			       interval has elapsed to avoid toggling due to multiple
			       packets. */
			       
				if (wParam == FRAME_TIMER_ID && broadcasting &&
					pClientData->broadcastEnd &&
					((GetTickCount() - pClientData->broadcastBeginTime) >
					 (BroadcastUnsubscribe * 1000L))) {
    				FORWARD_WM_MDIDESTROY(hwndMDIClient, hwnd, SendMessage);
					return 0;
				}    
				
				/* Here's a little bit of paranoia.  What if the socket
				   blocks or otherwise gets stuck and we don't get the
				   WM_SOCKET_SELECT/FD_WRITE message like we're guaranteed
				   to (right, tell me another) in the Winsock specification.
				   Well, if the socket's marked as busy, why not call select()
				   on every timer tick just to see if it's gotten unstuck
				   without bothering to let us know.  Since all we do is
				   clear the busy flag, this won't interfere with the
				   notification message even if it's on the way at the
				   time we arrive here. */
				   
				if (wParam == FRAME_TIMER_ID && pClientData->outputSocketBusy) {
					fd_set fds;
					struct timeval t;
					
					t.tv_sec = t.tv_usec = 0;
					FD_ZERO(&fds);
					FD_SET(pClientData->sReply, &fds);
					if (select(1, NULL, &fds, NULL, &t) > 0) {
						pClientData->outputSocketBusy = FALSE;
#ifdef OVERLOAD
						OutputDebugString("Timer cleared socket busy after select()\r\n");
#endif							
					}
				}
				
				/* If loopback packets are queued, we're not still
				   recording them, but we haven't yet started to play
				   them back (and aren't otherwise occupied with a sound file),
				   start the playback process. */
				
				if (wParam == FRAME_TIMER_ID &&
					(pClientData->localLoopback & LOOPBACK_ENABLED) &&
					!(pClientData->localLoopback & LOOPBACK_PLAYING) &&
					(pClientData->llhead != NULL) &&
					(pClientData->hFile == HFILE_ERROR &&
			    		pClientData->mmioHandle == NULL) &&
			    		!pClientData->wantsInput) {
			    	pClientData->localLoopback |= LOOPBACK_PLAYING;
			    	SetTimer(hwnd, 2, (UINT) 1000, NULL);
				}
				
				/* Button up timer has expired.  If we're still transmitting,
				   cease transmission. */
				   
				if (wParam == 8) {
					KillTimer(hwnd, 8);
					pClientData->buttonUpTimer = FALSE;
					if (pClientData->wantsInput) {
						SendMessage(hwnd, WM_CHAR, ' ', 0L);
					}
				}    
			     
			    /* Cadence timer indicating it's time to send the
			       next block of a sound file.  Read it in and send
			       it on its way. */
			
			    if (wParam == 2 && (pClientData->hFile != HFILE_ERROR ||
			    				    pClientData->mmioHandle != NULL ||
			    				    pClientData->localLoopback & LOOPBACK_PLAYING)) {
			    		long et;
			    		UINT bread = 0;

#ifdef MODEM			    		
			    		if (pClientData->modemConnection) {
			    			int err;
			    			COMSTAT cs;
			    			
			    			err = GetCommError(modemHandle, &cs);
			    			if (cs.cbOutQue > 1000) {
				    			/* If modem connection and modem's backed up
				    			   with output, spin until it goes idle. */
				            	SetTimer(hwnd, 2, 10, NULL);
			    				break;
			            	}
			    		}
#endif			    	
						if (pClientData->localLoopback & LOOPBACK_PLAYING) {
							while (outputPending < 10) {
								bread = loop_samples(pClientData);
								
								if (bread == 0) {
									pClientData->localLoopback &= ~LOOPBACK_PLAYING;
									KillTimer(hwnd, 2);
//OutputDebugString("End loopback replay.\r\n");									
									return 0;
								}
								SendMessage(hwndMDIFrame, loop_control_port(pClientData) ?
												WM_SOCKET_CONTROL : WM_SOCKET_SELECT,
												INVALID_SOCKET, (LPARAM) pClientData);
							}					
				            SetTimer(hwnd, 2, (UINT) 10, NULL);
				            return 0;
			    		} else if (pClientData->mmioHandle != NULL) {
			    			//	Queue next packet from .WAV input file
			    			if (!pClientData->quitSoundFile) {
			    				bread = readWaveNext(hwnd, pClientData);
			    			}
			    			if (bread == 0) {
			    				readWaveTerm(pClientData);
			    			} else {
			    				bread = (UINT) ((bread * 8000L) / 11025L);
			    			} 
			    		} else {
			    			//	Queue next packet from .AU input file
			    			if (!pClientData->quitSoundFile) {
					            bread = _lread(pClientData->hFile,
										sb.buffer.buffer_val, currentInputSamples);
					        }
				            if (bread == 0) {
				            	_lclose(pClientData->hFile);
				            	pClientData->hFile = HFILE_ERROR;
				            } else {
								sb.buffer.buffer_len = bread;
								/* Since we're manufacturing our own sound buffer
								   in place rather than calling CreateSoundBuffer(),
								   we need to apply whatever compression is requested
								   here before shipping the buffer. */
    							isWaveSound = TRUE;
    							sqpacket = FALSE;

	        					if (sb.buffer.buffer_len > 0) {
									createSoundBuffer(sb.buffer.buffer_val, (WORD) sb.buffer.buffer_len,
													  1, 8000, 8000, 0);
									shipSoundBuffer(hwnd, pClientData);
								} else {
									sqpacket = TRUE;
								}
				            }
			            }		

			            if (bread == 0) {
			            	KillTimer(hwnd, 2);
			            	pClientData->state = pClientData->wantsInput ? SendingLiveAudio : Idle;
			            	DragAcceptFiles(hwnd, TRUE);
#ifdef SHOW_STATE			            	
			    			InvalidateRect(hwnd, NULL, TRUE);
			    			UpdateWindow(hwnd);
#endif			    			
			    			return 0;
			            }
				        pClientData->cbSent += bread;
#ifdef SHOW_STATE
						InvalidateRect(hwnd, NULL, TRUE);
						UpdateWindow(hwnd);
#endif						

						// If window isn't immortal, reset the timeout
						if (pClientData->timeout > 0) {
							pClientData->timeout = 0;
						}
			
			            /* The following code is needed because when we're reading
			               sound from a file, as opposed to receiving it in real
			               time from the CODEC, we must meter out the samples
			               at the rate they will actually be played by the destination
			               machine.  For 8000 samples per second, this amounts
			               to 125 microseconds per sample, minus the time we spent
			               compressing the data (which is substantial for GSM) and
			               a fudge factor, kOverhead, which accounts for the time
			               spent in executing the delay itself and getting control
			               back after it's done.  If sound files pause periodically
			               (when the sending machine isn't loaded), you may need
			               to reduce the delay parameters.  If they're too low,
			               however, data will be lost when sending long sound files. */
			
#define kOverhead 25000
			            et = ((bread * 125L) - kOverhead) -
			            	((GetTickCount() - startTicks) * 1000);
			            if (et <= 0) {
			            	et = 10;
			            }
			            SetTimer(hwnd, 2, (UINT) (et / 1000), NULL);
#ifdef DBT            
if (!IsIconic(hwnd)) {
	HDC hdc = GetDC(hwnd);
							
	WinPrintf(hdc, 6, 1, "Timer reset to %d ms.", (UINT) (et / 1000));
	ReleaseDC(hwnd, hdc);		
}
#endif			
			    }
			    
			    /*	Timer indicating a periodic PGP poll is underway.
			       	If PGP has finished writing the decoded session key
			       	file, read it into the connection structure and put
			       	it into effect, then sweep up after PGP.  */
			       	
			    if (wParam == 3) {
			    	HFILE kfile = _lopen(pClientData->pgpFileName,
			    						OF_READWRITE | OF_SHARE_EXCLUSIVE);
			    	
			    	if (kfile == HFILE_ERROR) {
			    		//	Still not done.  Reset the timer
			    		SetTimer(hwnd, 3, 1000, NULL);
			    	} else {
			    		if (_lread(kfile, pClientData->pgpkey, 17) == 17) {
			    			char yfn[MAX_PATH];
		    				int i;
		    				unsigned char ow[16];
			    			
			    			pClientData->pgpkey[0] = TRUE;
			    			
			    			//	Overwrite the session key on disc
			    			
		    				_llseek(kfile, 0L, 0);
		    				for (i = 0; i < 16; i++) {
		    					ow[i] = 0xFF;
		    				}
		    				_lwrite(kfile, ow, 16);
		    				_llseek(kfile, 0L, 0);
		    				for (i = 0; i < 16; i++) {
		    					ow[i] = 0;
		    				}
		    				_lwrite(kfile, ow, 16);
			    			
			    			_lclose(kfile);
			    			strcpy(yfn, pClientData->pgpFileName);
			    			_unlink(yfn);
			    			strcat(yfn, ".TMP");
			    			_unlink(yfn);
			    			pClientData->pgpFileName[0] = 0;
			    			KillTimer(hwnd, 3); 
			    		} else {
			    			pClientData->pgpkey[0] = FALSE;
			    			SetTimer(hwnd, 3, 1000, NULL);
			    			_lclose(kfile);
			    		}
			    	}
			    }
			    
			    /*	Timer indicating a periodic PGP poll is underway.
			       	If PGP has finished writing the encoded session key
			       	file, read it into the connection structure and put
			       	it into effect, then sweep up after PGP.  */
			       	
			    if (wParam == 4) {
			    	HFILE kfile = _lopen(pClientData->opgpFileName,
			    					OF_READ | OF_SHARE_EXCLUSIVE);
			    	
			    	if (kfile == HFILE_ERROR) {
			    		//	Still not done.  Reset the timer
			    		SetTimer(hwnd, 4, 1000, NULL);
			    	} else {
			    		int len;
			    		
			    		if ((len = _lread(kfile, ebuf.buffer.buffer_val, BUFL)) > 0) {
			    			int i;
			    			char yfn[MAX_PATH];
			    			
			    			ebuf.buffer.buffer_len = len;
			    			pClientData->opgpkey[0] = TRUE;		// Activate outbound PGP key
			    			ebuf.compression = fProtocol | fKeyPGP;
			    			strcpy(ebuf.sendinghost, ourSendingHost);
					        revlong(&ebuf.compression);
					        revlong(&ebuf.buffer.buffer_len);
					        
					        for (i = 0; i < 3; i++) {
						        if (writeOutput(pClientData, (LPSTR) &ebuf,
						        		(int) ((sizeof(struct soundbuf) - BUFL) +
						        		len)) < 0) {
						        	break;
						        }
						    }
			    			_lclose(kfile);
			    			
			    			strcpy(yfn, pClientData->opgpFileName);
			    			_unlink(yfn);
				            yfn[strlen(yfn) - 3] = 0;
			    			strcat(yfn, "TMP");
			    			
			    			//	Overwrite the session key on disc

			    			kfile = _lopen(yfn, OF_READWRITE | OF_SHARE_EXCLUSIVE);
			    			if (kfile != HFILE_ERROR) {
			    				int i;
			    				unsigned char ow[16];
			    				
			    				for (i = 0; i < 16; i++) {
			    					ow[i] = 0xFF;
			    				}
			    				_lwrite(kfile, ow, 16);
			    				_llseek(kfile, 0L, 0);
			    				for (i = 0; i < 16; i++) {
			    					ow[i] = 0;
			    				}
			    				_lwrite(kfile, ow, 16);
			    				_lclose(kfile);
			    			}
			    			_unlink(yfn);
			    			pClientData->opgpFileName[0] = 0;
			    			KillTimer(hwnd, 4); 
			    		} else {
			    			SetTimer(hwnd, 4, 1000, NULL);
			    			_lclose(kfile);
			    		}
			    	}
			    }
			    
			    /*	Cadence timer regulating retrieval of a face
			    	image from the connected host.  */
			    
			    if (wParam == 5) {
					int makereq = FALSE;
		
					/* If a face file transfer is in progress, request
		               the next block or, if it's time, re-issue the last
					   request if the timeout has expired. */
		
					if (pClientData->face_stat == FSreply) {
						makereq = TRUE;
						pClientData->face_retry = 0;
#ifdef TRACE_FACE
						{	char s[MAX_HOST + 80];
						
		                    wsprintf(s, "Request face data at %ld from %s\r\n",
								pClientData->face_address, pClientData->szHost);
							OutputDebugString(s);
						}
#endif						
					} else if (pClientData->face_stat == FSrequest) {
						pClientData->face_timeout++;
						if (pClientData->face_timeout >= FaceTimeout) {
#ifdef TRACE_FACE
							{	char s[MAX_HOST + 80];
							
			                    wsprintf(s, "Retry %d reissue face data request at %ld from %s\r\n",
										pClientData->face_retry, pClientData->face_address,
										pClientData->szHost);
								OutputDebugString(s);
							}
#endif						
							if (pClientData->face_retry > FaceMaxRetries) {
/*							
								if (pClientData->face_file != NULL) {
									fclose(c->face_file);
									c->face_file = NULL;
								}
								unlink(c->face_filename);
								c->face_filename[0] = 0;
*/								
								pClientData->face_stat = FSabandoned;
								if (pClientData->face_bmp != NULL) {
									GlobalFreePtr(pClientData->face_bmp);
									pClientData->face_bmp = NULL;
								}
								KillTimer(hwnd, 5);
#ifdef TRACE_FACE
								{	char s[MAX_HOST + 80];
								
				                    wsprintf(s, "Timeout, no face image available for %s\r\n",
											pClientData->szHost);
									OutputDebugString(s);
								}
#endif						
							} else {
								makereq = TRUE;
								pClientData->face_retry++;
							}
						}
					} else {
						KillTimer(hwnd, 5);			// Terminated due to data format error
#ifdef TRACE_FACE
						OutputDebugString("Face timer shut down.\r\n");
#endif						
					}
		
					if (makereq) {
						pClientData->face_stat = FSrequest;
						pClientData->face_timeout = 0;
						sb.compression = htonl(fProtocol | fFaceData | faceRequest);
						strcpy(sb.sendinghost, ourSendingHost);
						sb.buffer.buffer_len = htonl(pClientData->face_address);
						if (writeOutput(pClientData, (LPSTR) &sb, sizeof(soundbuf) - BUFL) < 0) {
		                 	pClientData->face_stat = FSabandoned;
// *** CLEAN UP DEBRIS		                 	   
						}
					}
			    }
			}
        	return 0;
    }
    return DefMDIChildProc(hwnd, nMessage, wParam, lParam);
}


