/* 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 #include #include #include "SDL_net.h" #ifdef macintosh #include "GUI.h" #include "GUI_widgets.h" #else #include #include #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; iAddText("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\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; iAddText("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); }