From 012f9e4156919157bfff0d5ce8f105a04b0c4a70 Mon Sep 17 00:00:00 2001 From: tomsmeding Date: Fri, 17 Mar 2017 23:16:21 +0100 Subject: client: Start working on a simple client --- client/.gitignore | 1 + client/Makefile | 30 ++++ client/client.c | 418 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ client/global.c | 33 +++++ client/global.h | 14 ++ client/memory.c | 16 +++ client/memory.h | 15 ++ 7 files changed, 527 insertions(+) create mode 100644 client/.gitignore create mode 100644 client/Makefile create mode 100644 client/client.c create mode 100644 client/global.c create mode 100644 client/global.h create mode 100644 client/memory.c create mode 100644 client/memory.h 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(cursorai_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 "); + + 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;cmdilinelen){ + 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-line0){ + 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 +#include +#include +#include +#include +#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 +#include +#include +#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 + +#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); -- cgit v1.2.3-54-g00ecf