aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2017-03-17 23:16:21 +0100
committertomsmeding <tom.smeding@gmail.com>2017-03-17 23:16:21 +0100
commit012f9e4156919157bfff0d5ce8f105a04b0c4a70 (patch)
treecacce5438a0e6fa8baef4ee4ef96951d7b1dd8a4
parentadd6b39aece179a0e0ad425af72b8a043a0d3188 (diff)
client: Start working on a simple client
-rw-r--r--client/.gitignore1
-rw-r--r--client/Makefile30
-rw-r--r--client/client.c418
-rw-r--r--client/global.c33
-rw-r--r--client/global.h14
-rw-r--r--client/memory.c16
-rw-r--r--client/memory.h15
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);