/*
CHAT: A chat client using the SDL example network and GUI libraries
Copyright (C) 1997-2004 Sam Lantinga
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Sam Lantinga
5635-34 Springhouse Dr.
Pleasanton, CA 94588 (USA)
slouken@devolution.com
*/
/* Note that this isn't necessarily the way to run a chat system.
This is designed to excercise the network code more than be really
functional.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SDL_net.h"
#ifdef macintosh
#include "GUI.h"
#include "GUI_widgets.h"
#else
#include <GUI/GUI.h>
#include <GUI/GUI_widgets.h>
#endif
#include "chat.h"
/* Global variables */
static TCPsocket tcpsock = NULL;
static UDPsocket udpsock = NULL;
static SDLNet_SocketSet socketset = NULL;
static UDPpacket **packets = NULL;
static struct {
int active;
Uint8 name[256+1];
} people[CHAT_MAXPEOPLE];
static GUI *gui = NULL;
static GUI_TermWin *termwin;
static GUI_TermWin *sendwin;
enum image_names {
IMAGE_QUIT,
IMAGE_SCROLL_UP,
IMAGE_SCROLL_DN,
NUM_IMAGES
};
char *image_files[NUM_IMAGES] = {
"quit.bmp", "scroll_up.bmp", "scroll_dn.bmp"
};
SDL_Surface *images[NUM_IMAGES];
void SendHello(char *name)
{
IPaddress *myip;
char hello[1+1+256];
int i, n;
/* No people are active at first */
for ( i=0; i<CHAT_MAXPEOPLE; ++i ) {
people[i].active = 0;
}
if ( tcpsock != NULL ) {
/* Get our chat handle */
if ( (name == NULL) &&
((name=getenv("CHAT_USER")) == NULL) &&
((name=getenv("USER")) == NULL ) ) {
name="Unknown";
}
termwin->AddText("Using name '%s'\n", name);
/* Construct the packet */
hello[0] = CHAT_HELLO;
myip = SDLNet_UDP_GetPeerAddress(udpsock, -1);
memcpy(&hello[CHAT_HELLO_PORT], &myip->port, 2);
if ( strlen(name) > 255 ) {
n = 255;
} else {
n = strlen(name);
}
hello[CHAT_HELLO_NLEN] = n;
strncpy(&hello[CHAT_HELLO_NAME], name, n);
hello[CHAT_HELLO_NAME+n++] = 0;
/* Send it to the server */
SDLNet_TCP_Send(tcpsock, hello, CHAT_HELLO_NAME+n);
}
}
void SendBuf(char *buf, int len)
{
int i;
/* Redraw the prompt and add a newline to the buffer */
sendwin->Clear();
sendwin->AddText(CHAT_PROMPT);
buf[len++] = '\n';
/* Send the text to each of our active channels */
for ( i=0; i < CHAT_MAXPEOPLE; ++i ) {
if ( people[i].active ) {
if ( len > packets[0]->maxlen ) {
len = packets[0]->maxlen;
}
memcpy(packets[0]->data, buf, len);
packets[0]->len = len;
SDLNet_UDP_Send(udpsock, i, packets[0]);
}
}
}
void SendKey(SDLKey key, Uint16 unicode)
{
static char keybuf[80-sizeof(CHAT_PROMPT)+1];
static int keypos = 0;
unsigned char ch;
/* We don't handle wide UNICODE characters yet */
if ( unicode > 255 ) {
return;
}
ch = (unsigned char)unicode;
/* Add the key to the buffer, and send it if we have a line */
switch (ch) {
case '\0':
break;
case '\r':
case '\n':
/* Send our line of text */
SendBuf(keybuf, keypos);
keypos = 0;
break;
case '\b':
/* If there's data, back up over it */
if ( keypos > 0 ) {
sendwin->AddText((char *)&ch, 1);
--keypos;
}
break;
default:
/* If the buffer is full, send it */
if ( keypos == (sizeof(keybuf)/sizeof(keybuf[0]))-1 ) {
SendBuf(keybuf, keypos);
keypos = 0;
}
/* Add the text to our send buffer */
sendwin->AddText((char *)&ch, 1);
keybuf[keypos++] = ch;
break;
}
}
int HandleServerData(Uint8 *data)
{
int used;
switch (data[0]) {
case CHAT_ADD: {
Uint8 which;
IPaddress newip;
/* Figure out which channel we got */
which = data[CHAT_ADD_SLOT];
if ((which >= CHAT_MAXPEOPLE) || people[which].active) {
/* Invalid channel?? */
break;
}
/* Get the client IP address */
newip.host=SDLNet_Read32(&data[CHAT_ADD_HOST]);
newip.port=SDLNet_Read16(&data[CHAT_ADD_PORT]);
/* Copy name into channel */
memcpy(people[which].name, &data[CHAT_ADD_NAME], 256);
people[which].name[256] = 0;
people[which].active = 1;
/* Let the user know what happened */
termwin->AddText(
"* New client on %d from %d.%d.%d.%d:%d (%s)\n", which,
(newip.host>>24)&0xFF, (newip.host>>16)&0xFF,
(newip.host>>8)&0xFF, newip.host&0xFF,
newip.port, people[which].name);
/* Put the address back in network form */
newip.host = SDL_SwapBE32(newip.host);
newip.port = SDL_SwapBE16(newip.port);
/* Bind the address to the UDP socket */
SDLNet_UDP_Bind(udpsock, which, &newip);
}
used = CHAT_ADD_NAME+data[CHAT_ADD_NLEN];
break;
case CHAT_DEL: {
Uint8 which;
/* Figure out which channel we lost */
which = data[CHAT_DEL_SLOT];
if ( (which >= CHAT_MAXPEOPLE) ||
! people[which].active ) {
/* Invalid channel?? */
break;
}
people[which].active = 0;
/* Let the user know what happened */
termwin->AddText(
"* Lost client on %d (%s)\n", which, people[which].name);
/* Unbind the address on the UDP socket */
SDLNet_UDP_Unbind(udpsock, which);
}
used = CHAT_DEL_LEN;
break;
case CHAT_BYE: {
termwin->AddText("* Chat server full\n");
}
used = CHAT_BYE_LEN;
break;
default: {
/* Unknown packet type?? */;
}
used = 0;
break;
}
return(used);
}
void HandleServer(void)
{
Uint8 data[512];
int pos, len;
int used;
/* Has the connection been lost with the server? */
len = SDLNet_TCP_Recv(tcpsock, (char *)data, 512);
if ( len <= 0 ) {
SDLNet_TCP_DelSocket(socketset, tcpsock);
SDLNet_TCP_Close(tcpsock);
tcpsock = NULL;
termwin->AddText("Connection with server lost!\n");
} else {
pos = 0;
while ( len > 0 ) {
used = HandleServerData(&data[pos]);
pos += used;
len -= used;
if ( used == 0 ) {
/* We might lose data here.. oh well,
we got a corrupt packet from server
*/
len = 0;
}
}
}
}
void HandleClient(void)
{
int n;
n = SDLNet_UDP_RecvV(udpsock, packets);
while ( n-- > 0 ) {
if ( packets[n]->channel >= 0 ) {
termwin->AddText("[%s] ",
people[packets[n]->channel].name);
termwin->AddText((char *)packets[n]->data, packets[n]->len);
}
}
}
GUI_status HandleNet(void)
{
SDLNet_CheckSockets(socketset, 0);
if ( SDLNet_SocketReady(tcpsock) ) {
HandleServer();
}
if ( SDLNet_SocketReady(udpsock) ) {
HandleClient();
}
/* Redraw the screen if the window changed */
if ( termwin->Changed() ) {
return(GUI_REDRAW);
} else {
return(GUI_PASS);
}
}
void InitGUI(SDL_Surface *screen)
{
int x1, y1, y2;
SDL_Rect empty_rect = { 0, 0, 0, 0 };
GUI_Widget *widget;
gui = new GUI(screen);
/* Chat terminal window */
termwin = new GUI_TermWin(0, 0, 80*8, 50*8, NULL,NULL,CHAT_SCROLLBACK);
gui->AddWidget(termwin);
/* Send-line window */
y1 = termwin->H()+2;
sendwin = new GUI_TermWin(0, y1, 80*8, 1*8, NULL, SendKey, 0);
sendwin->AddText(CHAT_PROMPT);
gui->AddWidget(sendwin);
/* Add scroll buttons for main window */
y1 += sendwin->H()+2;
y2 = y1+images[IMAGE_SCROLL_UP]->h;
widget = new GUI_ScrollButtons(2, y1, images[IMAGE_SCROLL_UP],
empty_rect, 2, y2, images[IMAGE_SCROLL_DN],
SCROLLBAR_VERTICAL, termwin);
gui->AddWidget(widget);
/* Add QUIT button */
x1 = (screen->w-images[IMAGE_QUIT]->w)/2;
y1 = sendwin->Y()+sendwin->H()+images[IMAGE_QUIT]->h/2;
widget = new GUI_Button(NULL, x1, y1, images[IMAGE_QUIT], NULL);
gui->AddWidget(widget);
/* That's all folks */
return;
}
extern "C"
void cleanup(int exitcode)
{
int i;
/* Clean up the GUI */
if ( gui ) {
delete gui;
gui = NULL;
}
/* Clean up any images we have */
for ( i=0; i<NUM_IMAGES; ++i ) {
if ( images[i] ) {
SDL_FreeSurface(images[i]);
images[i] = NULL;
}
}
/* Close the network connections */
if ( tcpsock != NULL ) {
SDLNet_TCP_Close(tcpsock);
tcpsock = NULL;
}
if ( udpsock != NULL ) {
SDLNet_UDP_Close(udpsock);
udpsock = NULL;
}
if ( socketset != NULL ) {
SDLNet_FreeSocketSet(socketset);
socketset = NULL;
}
if ( packets != NULL ) {
SDLNet_FreePacketV(packets);
packets = NULL;
}
SDLNet_Quit();
SDL_Quit();
exit(exitcode);
}
main(int argc, char *argv[])
{
SDL_Surface *screen;
int i;
char *server;
IPaddress serverIP;
/* Check command line arguments */
if ( argv[1] == NULL ) {
fprintf(stderr, "Usage: %s <server>\n", argv[0]);
exit(1);
}
/* Initialize SDL */
if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) {
fprintf(stderr, "Couldn't initialize SDL: %s\n",SDL_GetError());
exit(1);
}
/* Set a 640x480 video mode -- allows 80x50 window using 8x8 font */
screen = SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE);
if ( screen == NULL ) {
fprintf(stderr, "Couldn't set video mode: %s\n",SDL_GetError());
SDL_Quit();
exit(1);
}
/* Initialize the network */
if ( SDLNet_Init() < 0 ) {
fprintf(stderr, "Couldn't initialize net: %s\n",
SDLNet_GetError());
SDL_Quit();
exit(1);
}
/* Get ready to initialize all of our data */
/* Load the display font and other images */
for ( i=0; i<NUM_IMAGES; ++i ) {
images[i] = NULL;
}
for ( i=0; i<NUM_IMAGES; ++i ) {
images[i] = SDL_LoadBMP(image_files[i]);
if ( images[i] == NULL ) {
fprintf(stderr, "Couldn't load '%s': %s\n",
image_files[i], SDL_GetError());
cleanup(2);
}
}
/* Go! */
InitGUI(screen);
/* Allocate a vector of packets for client messages */
packets = SDLNet_AllocPacketV(4, CHAT_PACKETSIZE);
if ( packets == NULL ) {
fprintf(stderr, "Couldn't allocate packets: Out of memory\n");
cleanup(2);
}
/* Connect to remote host and create UDP endpoint */
server = argv[1];
termwin->AddText("Connecting to %s ... ", server);
gui->Display();
SDLNet_ResolveHost(&serverIP, server, CHAT_PORT);
if ( serverIP.host == INADDR_NONE ) {
termwin->AddText("Couldn't resolve hostname\n");
} else {
/* If we fail, it's okay, the GUI shows the problem */
tcpsock = SDLNet_TCP_Open(&serverIP);
if ( tcpsock == NULL ) {
termwin->AddText("Connect failed\n");
} else {
termwin->AddText("Connected\n");
}
}
/* Try ports in the range {CHAT_PORT - CHAT_PORT+10} */
for ( i=0; (udpsock == NULL) && i<10; ++i ) {
udpsock = SDLNet_UDP_Open(CHAT_PORT+i);
}
if ( udpsock == NULL ) {
SDLNet_TCP_Close(tcpsock);
tcpsock = NULL;
termwin->AddText("Couldn't create UDP endpoint\n");
}
/* Allocate the socket set for polling the network */
socketset = SDLNet_AllocSocketSet(2);
if ( socketset == NULL ) {
fprintf(stderr, "Couldn't create socket set: %s\n",
SDLNet_GetError());
cleanup(2);
}
SDLNet_TCP_AddSocket(socketset, tcpsock);
SDLNet_UDP_AddSocket(socketset, udpsock);
/* Run the GUI, handling network data */
SendHello(argv[2]);
gui->Run(HandleNet);
cleanup(0);
/* Keep the compiler happy */
return(0);
}
syntax highlighted by Code2HTML, v. 0.9.1