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 /client | |
| parent | add6b39aece179a0e0ad425af72b8a043a0d3188 (diff) | |
client: Start working on a simple client
Diffstat (limited to '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); | 
