diff options
author | tomsmeding <tom.smeding@gmail.com> | 2017-03-17 23:16:21 +0100 |
---|---|---|
committer | tomsmeding <tom.smeding@gmail.com> | 2017-03-17 23:16:21 +0100 |
commit | 012f9e4156919157bfff0d5ce8f105a04b0c4a70 (patch) | |
tree | cacce5438a0e6fa8baef4ee4ef96951d7b1dd8a4 | |
parent | add6b39aece179a0e0ad425af72b8a043a0d3188 (diff) |
client: Start working on a simple client
-rw-r--r-- | client/.gitignore | 1 | ||||
-rw-r--r-- | client/Makefile | 30 | ||||
-rw-r--r-- | client/client.c | 418 | ||||
-rw-r--r-- | client/global.c | 33 | ||||
-rw-r--r-- | client/global.h | 14 | ||||
-rw-r--r-- | client/memory.c | 16 | ||||
-rw-r--r-- | client/memory.h | 15 |
7 files changed, 527 insertions, 0 deletions
diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..b051c6c --- /dev/null +++ b/client/.gitignore @@ -0,0 +1 @@ +client diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..e9bbb91 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,30 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c11 -g -fwrapv -I$(TERMIO)/include +LDFLAGS = -L$(TERMIO)/lib -ltermio + +TERMIO = $(HOME)/prefix + +TARGETS = client + +.PHONY: all clean remake + +# Clear all implicit suffix rules +.SUFFIXES: + +# Don't delete intermediate files +.SECONDARY: + +all: $(TARGETS) + +clean: + rm -f $(TARGETS) *.o + +remake: clean + $(MAKE) all + + +$(TARGETS): $(patsubst %.c,%.o,$(wildcard *.c)) + $(CC) -o $@ $^ $(LDFLAGS) + +%.o: %.c $(wildcard *.h) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/client/client.c b/client/client.c new file mode 100644 index 0000000..6609f89 --- /dev/null +++ b/client/client.c @@ -0,0 +1,418 @@ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include <string.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <netdb.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> +#include <termio.h> +#include "global.h" + + +static const Style normal_style={9,9,false,false}; +static const Style bold_style={9,9,true,false}; +static const Style command_style={3,9,false,false}; +static const Style server_style={6,9,false,false}; +static const Style error_style={1,9,true,false}; + + +static i64 uniqid(void){ + static i64 id=1; + return id++; +} + +static ssize_t send_all(int socket,const void *buffer,i64 length){ + ssize_t cursor=0; + while(cursor<length){ + ssize_t nwr=send(socket,buffer+cursor,length-cursor,0); + if(nwr<=0)return nwr; + cursor+=nwr; + } + return cursor; +} + + +static int connect_server(const char *hostname,int port){ + struct addrinfo hints,*res; + + memset(&hints,0,sizeof(hints)); + hints.ai_family=AF_UNSPEC; + hints.ai_socktype=SOCK_STREAM; + + char portbuf[16]; + snprintf(portbuf,16,"%d",port); + int ret=getaddrinfo(hostname,portbuf,&hints,&res); + if(ret!=0){ + fprintf(stderr,"getaddrinfo: %s\n",gai_strerror(ret)); + exit(1); + } + + int sock; + struct addrinfo *current=res; + while(current){ + sock=socket(res->ai_family,res->ai_socktype,res->ai_protocol); + if(sock!=-1){ + if(connect(sock,res->ai_addr,res->ai_addrlen)==0){ + break; + } + } + close(sock); + sock=-1; + current=current->ai_next; + } + freeaddrinfo(res); + + return sock; +} + +static bool termio_needs_reset=false; + +static void termio_reset(void){ + if(!termio_needs_reset)return; + endkeyboard(); + endscreen(); + termio_needs_reset=false; +} + +static void termio_init(void){ + initscreen(); + initkeyboard(false); + installCLhandler(true); + atexit(termio_reset); + termio_needs_reset=true; +} + + +static i64 bar_width=11; + +static char *user_buffer=NULL; +static i64 user_bufsz=0; +static i64 user_buflen=0; + +static int server_sock=-1; + + +enum log_type{ + LOG_MESSAGE, + LOG_SERVER, + LOG_COMMAND, + LOG_ERROR, +}; + +static void emit_log_line(enum log_type type,const char *head,const char *line){ + i64 headlen=strlen(head); + if(headlen+1>bar_width){ + bar_width=headlen+1; + } + Size termsize=gettermsize(); + assert(termsize.w>bar_width); + + i64 linelen=strlen(line); + i64 nlines=1,cursor=termsize.w-bar_width; + while(cursor<linelen){ + cursor+=termsize.w-bar_width; + nlines++; + } + + scrollterm(0,1,termsize.w,termsize.h-2,nlines); + + char buffer[termsize.w+1]; + + switch(type){ + case LOG_MESSAGE: setstyle(&normal_style); break; + case LOG_SERVER: setstyle(&server_style); break; + case LOG_COMMAND: setstyle(&command_style); break; + case LOG_ERROR: setstyle(&error_style); break; + } + + pushcursor(); + moveto(bar_width-1-headlen,termsize.h-1-nlines); + tprintf("%s",head); + cursor=0; + for(int y=0;y<nlines;y++){ + memcpy(buffer,line+cursor,termsize.w-bar_width); + buffer[termsize.w-bar_width]='\0'; + + moveto(bar_width,termsize.h-1-nlines); + tprintf("%s",buffer); + + cursor+=termsize.w-bar_width; + } + popcursor(); + redraw(); +} + +__attribute__((format (printf, 3, 4))) +static void emit_log_line_f(enum log_type type,const char *head,const char *format,...){ + va_list ap; + va_start(ap,format); + char *buf=NULL; + vasprintf(&buf,format,ap); + va_end(ap); + assert(buf); + emit_log_line(type,head,buf); + free(buf); +} + +static void redraw_prompt(void){ + Size termsize=gettermsize(); + moveto(0,termsize.h-1); + setstyle(&bold_style); + tprintf("> "); + + setstyle(&normal_style); + fillrect(2,termsize.h-1,termsize.w-2,1,' '); + tprintf("%s",user_buffer); + redraw(); +} + + +static char *server_user=NULL; +static char *server_room=NULL; + +static void send_message(const char *msg){ + if(server_user==NULL){ + emit_log_line(LOG_ERROR,"--","Cannot send messages while not logged in"); + return; + } + if(server_room==NULL){ + emit_log_line(LOG_ERROR,"--","Cannot send messages while not in a room"); + return; + } +} + +static void cmd_register(const char **args){ + emit_log_line_f(LOG_COMMAND,"##","/register %s %s",args[0],args[1]); +} + +static void cmd_login(const char **args){ + emit_log_line_f(LOG_COMMAND,"##","Login called with %s and %s",args[0],args[1]); +} + +static void cmd_quit(const char **args){ + close(server_sock); + exit(0); +} + +struct cmd_list_item{ + const char *name; + int nargs; + bool longlast; + void (*func)(const char **args); +}; + +static struct cmd_list_item cmd_list[]={ + {"register",2,true,cmd_register}, + {"login",2,true,cmd_login}, + {"quit",0,false,cmd_quit}, +}; +#define NCOMMANDS ((i64)(sizeof(cmd_list)/sizeof(cmd_list[0]))) + +static void execute_command(char *line){ + if(line[0]=='/'){ + send_message(line); + return; + } + + i64 linelen=strlen(line); + char *sepp=memchr(line,' ',linelen); + if(sepp==NULL)sepp=line+linelen; + *sepp='\0'; + i64 cmdlen=sepp-line; + i64 cmdi; + for(cmdi=0;cmdi<NCOMMANDS;cmdi++){ + if(strncmp(line,cmd_list[cmdi].name,cmdlen)==0){ + break; + } + } + + if(cmdi==NCOMMANDS){ + emit_log_line_f(LOG_ERROR,"--","Unknown command '%s'",line); + return; + } + + int nargs=cmd_list[cmdi].nargs; + char *args[nargs]; + i64 cursor=cmdlen+1; + while(line[cursor]==' ')cursor++; + + for(int i=0;i<nargs;i++){ + if(cursor>linelen){ + emit_log_line_f(LOG_ERROR,"--","Too few parameters to command '%s'",line); + return; + } + if(i==nargs-1&&cmd_list[cmdi].longlast){ + sepp=line+linelen; + } else { + sepp=memchr(line+cursor,' ',linelen-cursor); + if(sepp==NULL)sepp=line+linelen; + } + *sepp='\0'; + args[i]=line+cursor; + cursor=sepp-line+1; + while(line[cursor]==' ')cursor++; + } + if(sepp-line<linelen){ + emit_log_line_f(LOG_ERROR,"--","Too many parameters to command '%s'",line); + return; + } + + cmd_list[cmdi].func((const char**)args); +} + + +// Returns whether stdin was closed +static bool handle_stdin(void){ + int key=tgetkey(); + switch(key){ + case KEY_LF: + case KEY_CR: { + if(user_buffer[0]=='/'){ + execute_command(user_buffer+1); + } else { + send_message(user_buffer); + } + user_buflen=0; + user_buffer[0]='\0'; + redraw_prompt(); + break; + } + + case KEY_BACKSPACE: + if(user_buflen>0){ + user_buflen--; + user_buffer[user_buflen]='\0'; + redraw_prompt(); + } else { + bel(); + } + break; + + case KEY_CTRL+'U': + user_buflen=0; + user_buffer[0]='\0'; + redraw_prompt(); + break; + + default: + if(key>=32&&key<=126){ + if(user_buflen==user_bufsz-1){ + user_bufsz*=2; + user_buffer=realloc(user_buffer,user_bufsz,char); + } + user_buffer[user_buflen++]=key; + user_buffer[user_buflen]='\0'; + redraw_prompt(); + } else { + bel(); + } + break; + } + return false; +} + +// Returns whether socket was closed +static bool handle_server(void){ + static i64 bufsz=256,buflen=0; + static char *buffer=NULL; + if(buffer==NULL){ + buffer=malloc(bufsz,char); + } + + if(bufsz-buflen<128){ + bufsz*=2; + buffer=realloc(buffer,bufsz,char); + } + + ssize_t nr; + while(true){ + nr=read(server_sock,buffer+buflen,bufsz-buflen); + if(nr<0){ + if(errno==EINTR)continue; + if(errno==ECONNRESET){ + buflen=0; + return true; + } + perror("read"); + exit(1); + } + if(nr==0){ + buflen=0; + return true; + } + buflen+=nr; + break; + } + + char *lfp=memchr(buffer,'\n',buflen); + if(lfp==NULL)return false; + *lfp='\0'; + emit_log_line(LOG_SERVER,">>",buffer); + i64 linelen=lfp-buffer; + memmove(buffer,buffer+linelen+1,buflen-linelen-1); + buflen-=linelen+1; + return false; +} + + +int main(int argc,char **argv){ + const char *hostname="tomsmeding.com"; + int port=29536; + + if(argc>=2){ + hostname=argv[1]; + if(argc>=3){ + port=strtol(argv[2],NULL,10); + if(argc>=4){ + fprintf(stderr,"Usage: %s [host [port]]\n",argv[0]); + return 1; + } + } + } + + server_sock=connect_server(hostname,port); + if(server_sock==-1){ + fprintf(stderr,"Could not connect to %s port %d\n",hostname,port); + return 1; + } + + termio_init(); + + clearscreen(); + moveto(0,0); + setstyle(&bold_style); + tprintf("TOMSG CLIENT"); + + user_bufsz=256; + user_buflen=0; + user_buffer=malloc(user_bufsz,char); + + redraw_prompt(); + + while(true){ + fd_set inset; + FD_ZERO(&inset); + FD_SET(STDIN_FILENO,&inset); + FD_SET(server_sock,&inset); + int ret=select(server_sock+1,&inset,NULL,NULL,NULL); + if(ret==-1){ + if(errno==EINTR)continue; + perror("select"); + return 1; + } + if(FD_ISSET(STDIN_FILENO,&inset)){ + if(handle_stdin())break; + } + if(FD_ISSET(server_sock,&inset)){ + if(handle_server())break; + } + } + + close(server_sock); + termio_reset(); +} diff --git a/client/global.c b/client/global.c new file mode 100644 index 0000000..bf5bf2e --- /dev/null +++ b/client/global.c @@ -0,0 +1,33 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <errno.h> +#include "global.h" + +__attribute__((noreturn, format(printf, 1, 2))) +void die(const char *format,...){ + fprintf(stderr,"DIE: "); + va_list ap; + va_start(ap,format); + vfprintf(stderr,format,ap); + va_end(ap); + fputc('\n',stderr); + exit(1); +} + +__attribute__((noreturn)) +void die_perror(const char *func){ + fprintf(stderr,"DIE: %s: %s\n",func,strerror(errno)); + exit(1); +} + +__attribute__((format (printf, 1, 2))) +void debug(const char *format,...){ + fprintf(stderr,"DEBUG: "); + va_list ap; + va_start(ap,format); + vfprintf(stderr,format,ap); + va_end(ap); + fputc('\n',stderr); +} diff --git a/client/global.h b/client/global.h new file mode 100644 index 0000000..de0873b --- /dev/null +++ b/client/global.h @@ -0,0 +1,14 @@ +#pragma once + +#include <stdbool.h> +#include <stdint.h> +#include <inttypes.h> +#include "memory.h" + +typedef int64_t i64; +typedef uint64_t u64; + +void die(const char *format,...) __attribute__((noreturn, format(printf, 1, 2))); +void die_perror(const char *func) __attribute__((noreturn)); + +void debug(const char *format,...) __attribute__((format(printf, 1, 2))); diff --git a/client/memory.c b/client/memory.c new file mode 100644 index 0000000..aec3da2 --- /dev/null +++ b/client/memory.c @@ -0,0 +1,16 @@ +#include "global.h" +#include "memory.h" + +void* check_after_allocation(const char *func,size_t num,size_t sz,void *ptr){ + if(ptr==NULL){ + die("Allocation failed: %s(%zu * %zuB = %zu)",func,num,sz,num*sz); + } + return ptr; +} + +void* check_after_allocation_str(const char *func,void *ptr){ + if(ptr==NULL){ + die("Allocation failed: %s()",func); + } + return ptr; +} diff --git a/client/memory.h b/client/memory.h new file mode 100644 index 0000000..19cfb95 --- /dev/null +++ b/client/memory.h @@ -0,0 +1,15 @@ +#pragma once + +#include <stdlib.h> + +#define malloc(num,type) \ + ((type*)check_after_allocation("malloc",num,sizeof(type),malloc((num)*sizeof(type)))) +#define calloc(num,type) \ + ((type*)check_after_allocation("calloc",num,sizeof(type),calloc((num),sizeof(type)))) +#define realloc(ptr,num,type) \ + ((type*)check_after_allocation("realloc",num,sizeof(type),realloc((ptr),(num)*sizeof(type)))) +#define strdup(str) \ + ((char*)check_after_allocation_str("strdup",strdup(str))) + +void* check_after_allocation(const char *func,size_t num,size_t sz,void *ptr); +void* check_after_allocation_str(const char *func,void *ptr); |