#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "ui.h" #include "res.h" #include "proto.h" #include "chars.h" #include "common.h" #include "client.h" static char *conffile = 0; static const char *host = 0; static const char *tag = 0; static const char *port = 0; #define DEFAULT_MAX_OHISTORY 16777216 // bytes #define DEFAULT_MAX_IHISTORY 1048576 // bytes #define KILLRING_MAX_N 64 #define PONG_TIMEOUT 300 // seconds #define PING_INTERVAL 60 // seconds typedef enum { OHLK_NONE = 1, OHLK_INTERNAL, OHLK_SERVER, OHLK_CHANNEL, OHLK_PRIVATE, OHLK_TOCHAN, OHLK_TOPRIV, } OHLKIND; typedef enum { AGE_60SEC = 1, AGE_30MIN, AGE_24HOUR, AGE_7DAY, AGE_6MON, AGE_OLD, } AGECLASS; typedef enum { SUBK_NONE = 1, SUBK_P, SUBK_T, } SUBKIND; typedef struct sub SUB; typedef struct ohist OHIST; typedef struct ohline OHLINE; typedef struct config CONFIG; typedef struct hlopriv HLOPRIV; typedef struct edit EDIT; typedef struct to TO; typedef struct onchan ONCHAN; typedef struct ihist IHIST; typedef struct ihline IHLINE; typedef struct sbpt SBPT; typedef struct cstrh CSTRH; typedef struct bit BIT; typedef struct thist THIST; typedef struct xy XY; typedef struct wrhist WRHIST; typedef struct killed KILLED; typedef struct killring KILLRING; typedef struct reflex REFLEX; typedef struct react REACT; typedef struct repiece REPIECE; struct react { REFLEX *r; ES *e; const char *pstr; const char *tstr; char *err; } ; struct repiece { unsigned int flags; #define RPF_HAVERE 0x000001 char *match; regex_t re; int sub; regmatch_t *rm; } ; struct reflex { REFLEX *link; unsigned int flags; #define RF_ONCE 0x00000001 REPIECE p; REPIECE t; char *reaction; } ; struct wrhist { WRHIST *link; unsigned int value; unsigned int count; } ; struct xy { int x; int y; } ; struct cstrh { const char *text; const char *hl; int len; } ; #define NO_CSTRH ((CSTRH){.text=0,.hl=0,.len=0}) struct killed { KILLED *older; KILLED *newer; WSTR text; } ; struct killring { KILLED *oldest; KILLED *newest; int n; } ; struct sbpt { OHLINE *line; int lwl; } ; struct onchan { ONCHAN *link; SERVER *s; CSTR c; } ; struct to { TO *link; SERVER *on; char *dest; } ; struct scip { SERVER *srv; struct addrinfo *ai0; struct addrinfo *ai; char *text; int conn; } ; struct hlopriv { SERVER *s; ES text; } ; struct ircconn { ES modes_set; ES modes_clr; } ; struct config { SERVER *servers; const char *file; int line; int errs; } ; struct ohist { unsigned int size; OHLINE *oldest; OHLINE *newest; } ; struct thist { OHLINE *head; OHLINE **tailp; THIST *next; TSTAMP maxage; AGECLASS class; } ; struct ohline { OHLINE *anlink; OHLINE *aolink; OHLINE *tlink; AGECLASS age; OHLKIND kind; TSTAMP time; // BEGIN not used for OHLK_NONE CSTRH body; int cc; int zws; int fws; // END not used for OHLK_NONE union { // nothing for OHLK_NONE // nothing for OHLK_INTERNAL struct { // OHLK_SERVER SERVER *srv; CSTRH prefix; } server; struct { // OHLK_CHANNEL SERVER *srv; unsigned int flags; // see below CSTRH fullwho; CSTRH nickwho; CSTRH chan; } channel; struct { // OHLK_PRIVATE SERVER *srv; unsigned int flags; // see below CSTRH fullwho; CSTRH nickwho; } private; struct { // OHLK_TOCHAN SERVER *srv; unsigned int flags; // see below CSTRH chan; } tochan; struct { // OHLK_TOPRIV SERVER *srv; unsigned int flags; // see below CSTRH who; } topriv; } ; unsigned int flags; #define OHLF_HIDDEN 0x00000001 unsigned int size; int dlc; int dl_; CSTRH *dtext; char *dfree; int y; } ; struct ihist { unsigned int size; IHLINE *oldest; IHLINE *newest; } ; struct ihline { IHLINE *nlink; IHLINE *olink; CSTR text; } ; struct edit { ES pref; ES line; int curs; int mark; void *icookie; void *nexticookie; IHLINE *hcurs; IHIST ihist; } ; struct bit { const char *name; const char *text; unsigned int bit; } ; int debug = 0; int debugn = 0; char *login = 0; char *fullname = 0; char *pass = 0; static char *nick = 0; static int njoins = 0; static char **joins = 0; static char *protocol = 0; static REFLEX *reflexes = 0; static REFLEX **reflextail = &reflexes; static CONFIG config; static OHIST ohist; static unsigned int histshow; #define HS_LT 0x00000001 #define HS_TS 0x00000002 #define HS_FTS 0x00000004 #define HS_HIDDEN 0x00000008 #define HS_STATS 0x00000010 static const BIT histshow_bits[] = { { "lt", "line types", HS_LT }, { "ts", "timestamps", HS_TS }, { "fts", "full timestamps", HS_FTS }, { "hidden", "hidden lines", HS_HIDDEN }, { "stats", "debugging counts", HS_STATS }, { 0 } }; static EDIT *ie; static void (*ie_nextchar)(unsigned char); static EDIT ie_normal; static EDIT ie_slash; static EDIT ie_colon; static int redraw_id; static unsigned int want_redraw; #define WR_HIST 0x00000001 #define WR_INPUT 0x00000002 #define WR_WINCH 0x00000004 #define WR_CLOCK 0x00000008 #define WR_BEEP 0x00000010 #define WR_MODELINE 0x00000020 #define WR_ALL (~0U) static WRHIST *wrhist; static int lastilines; static int tiid; static TSTAMP last_input; static TO *to; static ONCHAN *listens; static char icookie_hist_n_p; static char icookie_delete; static int del_at; static int del_n; static char icookie_yank; static int yank_at; static int yank_n; static KILLED *yank_k; // scrollback holds the top-of-screen line/lwl // lwl can be -ve if line is the oldest line static SBPT scrollback; static ES sb_countstr; static int sb_countlen; static unsigned long long int sbserial; static unsigned long long int sblastout; static volatile sig_atomic_t winchpend; static int winchpipe[2]; static int winchid; static SERVER *onserver; static THIST thist_60sc; // last 60 seconds: ss.fffff static THIST thist_30mn; // last 30 minutes: mm:ss.ff static THIST thist_24hr; // 30min-24hr: hh:mm:ss static THIST thist_7day; // 24hr-7day: dd hh:mm static THIST thist_6mon; // 7day-6mon: mm-dd hh static THIST thist_old; // >6mon: yy-mm-dd static int agerid; static int clockid; static TSTAMP nexttick; static int noseconds = 0; static int clock_show_seconds; static KILLRING killring; static unsigned long long int outserial; #define Cisspace(x) isspace((unsigned char)(x)) #define Cisdigit(x) isdigit((unsigned char)(x)) extern const PROTOOPS ops_irc; extern const PROTOOPS ops_xmpp; // forwards static int start_connection(void *); static void slash_command(const char *, int); static void nextchar_normal(unsigned char); static void nextchar_viewsb(unsigned char); static int scip_connect(void *); static void plain_text(const char *, int, int); static void debugwait(void) { volatile int go = 0; while (! go) poll(0,0,100); } static int scan_subs( const char *s, void (*plain)(void *, const char *, int), void (*sub)(void *, SUBKIND, int), void (*err)(void *, const char *, ...), void *cbarg ) { int b; int n; int dv; int x0; int x; SUBKIND defsk; SUBKIND thissk; void end_plain(void) { if (x0 >= 0) { (*plain)(cbarg,s+x0,x-x0); x0 = -1; } } void set_plain(void) { if (x0 < 0) x0 = x; } int callsub(int n) { if (thissk == SUBK_NONE) { if (defsk == SUBK_NONE) { (*err)(cbarg,"no match kind specified"); return(1); } (*sub)(cbarg,defsk,n); } else { (*sub)(cbarg,thissk,n); } return(0); } b = 0; x0 = -1; for (x=0;s[x];x++) { switch (b) { case 0: if (*s == '\\') { end_plain(); thissk = SUBK_NONE; b = 1; } else { set_plain(); } break; case 1: switch (*s) { case 'T': defsk = SUBK_T; b = 0; break; case 'P': defsk = SUBK_P; b = 0; break; case 't': thissk = SUBK_T; break; case 'p': thissk = SUBK_P; break; case '(': b = 2; n = 0; break; case 0: n = 0; if (0) { case 1: n = 1; } if (0) { case 2: n = 2; } if (0) { case 3: n = 3; } if (0) { case 4: n = 4; } if (0) { case 5: n = 5; } if (0) { case 6: n = 6; } if (0) { case 7: n = 7; } if (0) { case 8: n = 8; } if (0) { case 9: n = 9; } if (callsub(n)) return(1); b = 0; break; case '\\': (*plain)(cbarg,s+x,1); break; default: (*err)(cbarg,"unrecognized replacement escape \\%c",*s); return(1); break; } break; case 2: switch (*s) { case ')': if (callsub(n)) return(1); b = 0; break; case 0: dv = 0; if (0) { case 1: dv = 1; } if (0) { case 2: dv = 2; } if (0) { case 3: dv = 3; } if (0) { case 4: dv = 4; } if (0) { case 5: dv = 5; } if (0) { case 6: dv = 6; } if (0) { case 7: dv = 7; } if (0) { case 8: dv = 8; } if (0) { case 9: dv = 9; } n = (n * 10) + dv; break; default: (*err)(cbarg,"bad digit %c in \\(...) escape",*s); return(1); break; } break; } } end_plain(); return(0); } static void set_nsub_plain( void *arg __attribute__((__unused__)), const char *text __attribute__((__unused__)), int len __attribute__((__unused__)) ) { } static void set_nsub_sub(void *rv, SUBKIND kind, int sub) { REFLEX *r; REPIECE *p; r = rv; switch (kind) { case SUBK_P: p = &r->p; break; case SUBK_T: p = &r->t; break; default: abort(); break; } if (sub >= p->sub) p->sub = sub + 1; } static void set_nsub_err(void *rv __attribute__((__unused__)), const char *fmt, ...) { va_list ap; char *m; va_start(ap,fmt); vasprintf(&m,fmt,ap); va_end(ap); fprintf(stderr,"%s: %s\n",__progname,m); } static void init_repiece(REPIECE *p, const char *pat) { p->flags = 0; p->match = strdup(pat); p->sub = 0; p->rm = 0; } static int compile_repiece(REPIECE *p) { int e; char *ebuf; size_t elen; e = regcomp(&p->re,p->match,REG_EXTENDED|((p->sub<1)?REG_NOSUB:0)); if (! e) { p->flags |= RPF_HAVERE; p->rm = malloc(((p->sub<1)?1:p->sub)*sizeof(regmatch_t)); return(0); } elen = regerror(e,&p->re,0,0); ebuf = malloc(elen); regerror(e,&p->re,ebuf,elen); fprintf(stderr,"%s: %s: %s\n",__progname,p->match,ebuf); free(ebuf); /* * It is not clear from regex(3) whether we need to regfree(). * UTSLing reveals that not only is it not necessary, it can be * actively wrong in some cases (eg, some early malloc failures lead * to an error return before touching the regex_t at all). */ return(1); } static void repiece_drop(REPIECE *p) { free(p->match); if (p->flags & RPF_HAVERE) regfree(&p->re); free(p->rm); } static void reflex_free(REFLEX *r) { repiece_drop(&r->p); repiece_drop(&r->t); free(r->reaction); free(r); } static void add_reflex(unsigned int flags, const char *ppat, const char *tpat, const char *response) { REFLEX *r; r = malloc(sizeof(REFLEX)); r->flags = flags; init_repiece(&r->p,ppat); init_repiece(&r->t,tpat); r->reaction = strdup(response); do { if (scan_subs(response,&set_nsub_plain,&set_nsub_sub,&set_nsub_err,r)) break; if (compile_repiece(&r->p) || compile_repiece(&r->t)) break; r->link = 0; *reflextail = r; reflextail = &r->link; return; } while (0); reflex_free(r); } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! host) { host = *av; } else { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs ++; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-config")) { WANTARG(); conffile = av[skip]; continue; } if (!strcmp(*av,"-login")) { WANTARG(); login = strdup(av[skip]); continue; } if (!strcmp(*av,"-name")) { WANTARG(); fullname = strdup(av[skip]); continue; } if (!strcmp(*av,"-nick")) { WANTARG(); nick = av[skip]; continue; } if (!strcmp(*av,"-pass")) { WANTARG(); pass = strdup(av[skip]); bzero(av[skip],strlen(av[skip])); continue; } if (!strcmp(*av,"-host")) { WANTARG(); host = av[skip]; continue; } if (!strcmp(*av,"-tag")) { WANTARG(); tag = av[skip]; continue; } if (!strcmp(*av,"-port")) { WANTARG(); port = av[skip]; continue; } if (!strcmp(*av,"-join")) { WANTARG(); joins = realloc(joins,(njoins+1)*sizeof(*joins)); joins[njoins++] = av[skip]; continue; } if (!strcmp(*av,"-proto")) { WANTARG(); protocol = av[skip]; continue; } if (!strcmp(*av,"-debug")) { debug = 1; continue; } if (!strcmp(*av,"-debugwait")) { debugwait(); continue; } if (!strcmp(*av,"-reflex1")) { char *ppat; char *tpat; char *resp; WANTARG(); ppat = av[skip]; WANTARG(); tpat = av[skip]; WANTARG(); resp = av[skip]; add_reflex(RF_ONCE,ppat,tpat,resp); continue; } if (!strcmp(*av,"-reflex")) { char *ppat; char *tpat; char *resp; WANTARG(); ppat = av[skip]; WANTARG(); tpat = av[skip]; WANTARG(); resp = av[skip]; add_reflex(0,ppat,tpat,resp); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void set_nbio(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } char *blk_to_nt(const char *data, int len) { char *s; s = malloc(len+1); bcopy(data,s,len); s[len] = '\0'; return(s); } WSTR nt_to_wstr(const char *text) { int l; char *s; l = strlen(text); s = malloc(l); bcopy(text,s,l); return((WSTR){.text=s,.len=l}); } WSTR wstr_copy_cstr(CSTR cs) { WSTR r; r.text = malloc(cs.len); bcopy(cs.text,r.text,cs.len); r.len = cs.len; return(r); } CSTR wstr_as_cstr(WSTR ws) { return((CSTR){.text=ws.text,.len=ws.len}); } static WSTR wstr_copy_pl(const void *data, int len) { WSTR r; r.text = malloc(len); bcopy(data,r.text,len); r.len = len; return(r); } static WSTR wstr_copy_nt(const char *s) { WSTR r; r.len = strlen(s); r.text = malloc(r.len); bcopy(s,r.text,r.len); return(r); } TSTAMP ts_now(void) { struct timeval tv; gettimeofday(&tv,0); return((tv.tv_sec*TS_SECONDS)+(tv.tv_usec*(TSTAMP)1)); } static void init_thist(THIST *th, AGECLASS class, unsigned int secs, THIST *next) { th->head = 0; th->tailp = &th->head; th->next = next; th->maxage = secs * 1000000ULL; th->class = class; } static void init_history(void) { ohist.size = 0; ohist.oldest = 0; ohist.newest = 0; histshow = 0; init_thist(&thist_60sc,AGE_60SEC,60,&thist_30mn); init_thist(&thist_30mn,AGE_30MIN,30*60,&thist_24hr); init_thist(&thist_24hr,AGE_24HOUR,24*60*60,&thist_7day); init_thist(&thist_7day,AGE_7DAY,7*24*60*60,&thist_6mon); // This number is 365.2425 * 24 * 60 * 60 / 2, half of a mean year init_thist(&thist_6mon,AGE_6MON,15778476,&thist_old); init_thist(&thist_old,AGE_OLD,0,0); } static void thist_append(THIST *th, OHLINE *l) { l->tlink = 0; *th->tailp = l; th->tailp = &l->tlink; l->age = th->class; } static int age_history(void *arg __attribute__((__unused__))) { TSTAMP now; int ms; void age_out(THIST *list) { TSTAMP limit; OHLINE *l; TSTAMP delta; limit = now - list->maxage; while (1) { l = list->head; if (! l) return; if (l->time > limit) { delta = ((l->time - limit) / 1000ULL) + 100; if (delta < ms) ms = delta; return; } list->head = l->tlink; if (! list->head) list->tailp = &list->head; if ((histshow & (HS_TS|HS_FTS)) && (l->dlc >= 0)) { l->dlc = -1; want_redraw |= WR_HIST; } thist_append(list->next,l); } } now = ts_now(); ms = 3600000; age_out(&thist_60sc); age_out(&thist_30mn); age_out(&thist_24hr); age_out(&thist_7day); age_out(&thist_6mon); if (ms < 100) ms = 100; return(ms); } static void start_ager(void) { agerid = aio_add_block(&age_history,0); } static int repiece_match(REPIECE *p, const char *b, int l) { int e; char *ebuf; size_t elen; p->rm[0].rm_so = 0; p->rm[0].rm_eo = l; if (! b) b = ""; e = regexec(&p->re,b,p->sub,p->rm,REG_STARTEND); if (e == REG_NOMATCH) { //hlprintf_int("Match: pat=%s txt=%.*s: fail",p->match,l,b); return(0); } if (e == 0) { //hlprintf_int("Match: pat=%s txt=%.*s: success",p->match,l,b); return(1); } elen = regerror(e,&p->re,0,0); ebuf = malloc(elen); regerror(e,&p->re,ebuf,elen); hlprintf_int("Internal: regexec failure for %s: %s",p->match,ebuf); free(ebuf); return(0); } static int reflex_match(REFLEX *r, const char *pbody, int plen, const char *tbody, int tlen) { return(repiece_match(&r->p,pbody,plen)&&repiece_match(&r->t,tbody,tlen)); } static void react_plain(void *rv, const char *text, int len) { es_append_n(((REACT *)rv)->e,text,len); } static void react_sub(void *rv, SUBKIND kind, int sub) { REACT *r; REPIECE *p; regmatch_t *rm; const char *s; r = rv; switch (kind) { case SUBK_P: p = &r->r->p; s = r->pstr; break; case SUBK_T: p = &r->r->t; s = r->tstr; break; default: abort(); break; } if ((sub < 0) || (sub >= p->sub)) { es_append_printf(r->e,"",sub,p->sub); } else { rm = &p->rm[sub]; es_append_n(r->e,s+rm->rm_so,rm->rm_eo-rm->rm_so); } } static void react_err(void *rv, const char *fmt, ...) { va_list ap; va_start(ap,fmt); vasprintf(&((REACT *)rv)->err,fmt,ap); va_end(ap); } static void reflex_react(SERVER *s, REFLEX *r, const char *pbody, const char *tbody) { REACT react; ES e; char *p; int l; es_init(&e); do { react.r = r; react.e = &e; react.pstr = pbody; react.tstr = tbody; react.err = 0; if (scan_subs(r->reaction,&react_plain,&react_sub,&react_err,&react)) { hlprintf_int("Reaction error: %s",react.err); break; } p = es_buf(&e); l = es_len(&e); if (l < 1) { hlprintf_int("reaction: empty resulting string: %s",r->reaction); break; } switch (p[0]) { case '"': plain_text(p+1,l-1,0); break; case ':': plain_text(p+1,l-1,1); break; case '/': onserver = s; slash_command(p+1,l-1); break; default: hlprintf_int("reaction: unrecognized first character %c: %s",p[0],r->reaction); break; } } while (0); es_done(&e); } static void maybe_react(SERVER *s, const char *pbody, int plen, const char *tbody, int tlen) { REFLEX *r; REFLEX **rp; rp = &reflexes; while ((r = *rp)) { if (reflex_match(r,pbody,plen,tbody,tlen)) { reflex_react(s,r,pbody,tbody); if (r->flags & RF_ONCE) { *rp = r->link; reflex_free(r); continue; } } rp = &r->link; } } static void ohist_drop(OHLINE *hl) { if (scrollback.line == hl) { scrollback.line = hl->anlink; scrollback.lwl = 0; want_redraw |= WR_HIST; } ohist.size -= hl->size; if (hl->anlink) hl->anlink->aolink = hl->aolink; else ohist.newest = hl->aolink; if (hl->aolink) hl->aolink->anlink = hl->anlink; else ohist.oldest = hl->anlink; free(hl->dtext); free(hl->dfree); free(hl); } static void ohl_append_common(OHLINE *hl) { hl->time = ts_now(); hl->dlc = -1; hl->dtext = 0; hl->dfree = 0; hl->anlink = 0; thist_append(&thist_60sc,hl); hl->aolink = ohist.newest; ohist.newest = hl; if (hl->aolink) hl->aolink->anlink = hl; else ohist.oldest = hl; ohist.size += hl->size; while (ohist.size > DEFAULT_MAX_OHISTORY) ohist_drop(ohist.oldest); want_redraw |= WR_HIST; outserial ++; } static int dispmap(unsigned char c, char *db, char nextc) { if ( ((c >= ' ') && (c <= '~')) || ((c >= 160) && (c <= 255)) ) { if (db) db[0] = c; return(1); } else if (c < 32) { if (db) { db[0] = '^'; db[1] = c ^ 64; } return(2); } else { if (Cisdigit(nextc) || (c > 077)) { if (db) { db[0] = '\\'; db[1] = "01234567"[c>>6]; db[2] = "01234567"[(c>>3)&7]; db[3] = "01234567"[c&7]; } return(4); } else if (c > 07) { if (db) { db[0] = '\\'; db[1] = "01234567"[c>>3]; db[2] = "01234567"[c&7]; } return(3); } else { if (db) { db[0] = '\\'; db[1] = "01234567"[c]; } return(2); } } } static OHLINE *setup_ohline(int npcs, ...) { va_list ap; const char *pp; int pl; int displ; int lastc; char *db; char *hb; CSTRH *sp; CSTRH *lastsp; int needhl; int i; int j; int cl; OHLINE *hl; int px; void pass1_flush(void) { cl = dispmap(lastc,0,pp[i]); displ += cl; if (cl != 1) needhl = 1; } void pass2_flush(void) { if (! lastsp->text) { lastsp->text = db; lastsp->hl = hb; lastsp->len = 0; } cl = dispmap(lastc,db,'\0'); db += cl; if (needhl) for (j=cl;j>0;j--) *hb++ = (cl != 1); lastsp->len += cl; } displ = 0; needhl = 0; lastc = -1; va_start(ap,npcs); for (px=npcs;px>0;px--) { pp = va_arg(ap,const char *); pl = va_arg(ap,int); va_arg(ap,CSTRH *); for (i=0;i= 0) pass1_flush(); lastc = (unsigned char)pp[i]; } } if (lastc >= 0) pass1_flush(); va_end(ap); if (needhl) { hl = malloc(sizeof(OHLINE)+(2*displ)); db = (void *) (hl + 1); hb = db + displ; } else { hl = malloc(sizeof(OHLINE)+(2*displ)); db = (void *) (hl + 1); hb = 0; } hl->anlink = 0; hl->aolink = 0; hl->tlink = 0; hl->age = AGE_OLD; hl->kind = OHLK_NONE; hl->time = 0; hl->flags = 0; hl->size = 0; hl->dlc = -1; hl->dl_ = -1; hl->dtext = 0; hl->dfree = 0; hl->y = -1; lastsp = 0; va_start(ap,npcs); for (px=npcs;px>0;px--) { pp = va_arg(ap,const char *); pl = va_arg(ap,int); sp = va_arg(ap,CSTRH *); sp->text = 0; sp->hl = 0; sp->len = 0; for (i=0;ibody.len; tp = hl->body.text; hp = hl->body.hl; hl->cc = 0; hl->zws = 0; hl->fws = 0; for (i=1;icc ++; for (i=8;izws ++; } if ( (tp[i-8] == (char)0xe3) && !hp[i-8] && !bcmp(tp+i-7,"\\200\\200",8) && !bcmp(hp+i-7,"\1\1\1\1\1\1\1\1",8) ) { hl->fws ++; } } if ( (hl->zws > 3) || (hl->fws > 3) || ((l > 20) && (hl->cc*8 > l)) ) { hl->flags |= OHLF_HIDDEN; } } static void ohl_append_internal(const char *bodys, int bodyl) { OHLINE *hl; CSTRH b; if (! bodyl) return; hl = setup_ohline(1,bodys,bodyl,&b); hl->kind = OHLK_INTERNAL; hl->body = b; hl->size = b.len; maybe_hide(hl); ohl_append_common(hl); } void ohl_append_server(SERVER *s, const char *pfxs, int pfxl, const char *bodys, int bodyl) { OHLINE *hl; CSTRH p; CSTRH b; if (! bodyl) return; hl = setup_ohline(2,pfxs,pfxl,&p,bodys,bodyl,&b); hl->kind = OHLK_SERVER; hl->server.srv = s; hl->server.prefix = p; hl->body = b; hl->size = b.len; maybe_hide(hl); maybe_react(s,pfxs,pfxl,bodys,bodyl); ohl_append_common(hl); } void ohl_append_channel(SERVER *s, const char *fwho, int fwlen, const char *nwho, int nwlen, const char *chan, int clen, const char *body, int blen, unsigned int flags) { OHLINE *hl; CSTRH fw; CSTRH nw; CSTRH ch; CSTRH bd; hl = setup_ohline(4,fwho,fwlen,&fw,nwho,nwlen,&nw,chan,clen,&ch,body,blen,&bd); hl->kind = OHLK_CHANNEL; hl->body = bd; hl->channel.srv = s; hl->channel.flags = flags; hl->channel.fullwho = fw; hl->channel.nickwho = nw; hl->channel.chan = ch; hl->size = fw.len + nw.len + ch.len + bd.len; maybe_hide(hl); ohl_append_common(hl); } void ohl_append_private(SERVER *s, const char *fwho, int fwlen, const char *nwho, int nwlen, const char *body, int blen, unsigned int flags) { OHLINE *hl; CSTRH fw; CSTRH nw; CSTRH bd; hl = setup_ohline(3,fwho,fwlen,&fw,nwho,nwlen,&nw,body,blen,&bd); hl->kind = OHLK_PRIVATE; hl->body = bd; hl->private.srv = s; hl->private.flags = flags; hl->private.fullwho = fw; hl->private.nickwho = nw; hl->size = fw.len + nw.len + bd.len; maybe_hide(hl); ohl_append_common(hl); } static void ohl_append_tochan(SERVER *s, const char *chan, int clen, const char *body, int blen, unsigned int flags) { OHLINE *hl; CSTRH ch; CSTRH bd; hl = setup_ohline(2,chan,clen,&ch,body,blen,&bd); hl->kind = OHLK_TOCHAN; hl->body = bd; hl->tochan.srv = s; hl->tochan.flags = flags; hl->tochan.chan = ch; hl->size = ch.len + bd.len; maybe_hide(hl); ohl_append_common(hl); } static void ohl_append_topriv(SERVER *s, const char *who, int wlen, const char *body, int blen, unsigned int flags) { OHLINE *hl; CSTRH wh; CSTRH bd; hl = setup_ohline(2,who,wlen,&wh,body,blen,&bd); hl->kind = OHLK_TOPRIV; hl->body = bd; hl->topriv.srv = s; hl->tochan.flags = flags; hl->topriv.who = wh; hl->size = wh.len + bd.len; maybe_hide(hl); ohl_append_common(hl); } void hlprintf_int(const char *fmt, ...) { va_list ap; char *s; int l; va_start(ap,fmt); l = vasprintf(&s,fmt,ap); va_end(ap); ohl_append_internal(s,l); free(s); } void hlprintf_srv(SERVER *s, const char *fmt, ...) { va_list ap; char *t; int l; va_start(ap,fmt); l = vasprintf(&t,fmt,ap); va_end(ap); ohl_append_server(s,0,0,t,l); free(t); } static int hlo_w(void *pv, const char *data, int len) { es_append_n(&((HLOPRIV *)pv)->text,data,len); return(len); } static int hlo_c(void *pv) { HLOPRIV *p; p = pv; if (p->s) { ohl_append_server(p->s,0,0,es_buf(&p->text),es_len(&p->text)); } else { ohl_append_internal(es_buf(&p->text),es_len(&p->text)); } es_done(&p->text); free(p); return(0); } FILE *hline_open(SERVER *s) { FILE *f; HLOPRIV *p; p = malloc(sizeof(HLOPRIV)); if (! p) return(0); f = funopen(p,0,&hlo_w,0,&hlo_c); if (! f) { free(p); return(0); } p->s = s; es_init(&p->text); return(f); } static int listening_to(SERVER *s, const char *chan, int clen) { ONCHAN *o; for (o=listens;o;o=o->link) { if ((o->s == s) && (o->c.len == clen) && !bcmp(o->c.text,chan,clen)) return(1); } return(0); } void rerender_hist(void) { OHLINE *hl; for (hl=ohist.oldest;hl;hl=hl->anlink) hl->dlc = -1; want_redraw |= WR_HIST; } static void scip_done(SCIP *scip) { resolver_free_ailist(scip->ai0); free(scip->text); free(scip); } static void scip_abort(SCIP *scip) { if (scip->conn >= 0) close(scip->conn); if (scip->srv->pid) aio_remove_poll(scip->srv->pid); scip->srv->pid = AIO_NOID; if (scip->srv->bid) aio_remove_block(scip->srv->bid); scip->srv->bid = AIO_NOID; scip_done(scip); } static void server_dead(SERVER *s) { hlprintf_int("Connection to %s (%s) closed.",s->stext,s->ctext); if (s->pid != AIO_NOID) aio_remove_poll(s->pid); s->pid = AIO_NOID; if (s->bid != AIO_NOID) aio_remove_block(s->bid); s->bid = AIO_NOID; if (s->s >= 0) close(s->s); s->s = -1; free(s->ctext); s->ctext = 0; aio_oq_flush(&s->oq); (*s->ops->disconnected)(s); free(s->servname.text); s->servname = NO_WSTR; if (s->scip) { scip_done(s->scip); s->scip = 0; } if (s->pingid != AIO_NOID) { aio_remove_block(s->pingid); s->pingid = AIO_NOID; } } static SERVER *new_server(void) { SERVER *s; s = malloc(sizeof(SERVER)); s->stext = 0; s->tag = NO_WSTR; s->host = 0; s->port = 0; s->nick = NO_WSTR; s->nchan = 0; s->chans = 0; s->s = -1; s->ctext = 0; aio_oq_init(&s->oq); s->pid = AIO_NOID; s->bid = AIO_NOID; s->servname = NO_WSTR; s->scip = 0; s->reconn_delay = -1; s->ops = 0; s->priv = 0; return(s); } static void server_noids(SERVER *s) { if ((s->pid != AIO_NOID) || (s->bid != AIO_NOID)) abort(); } static int noserver(const char *what) { if (onserver) return(0); if (! config.servers) { hlprintf_int("You must configure a server before you can %s",what); } else { hlprintf_int("You have multiple servers, so you need to /on in order to %s",what); } return(1); } static void plain_text(const char *text, int len, int action) { TO *t; int dl; if (! config.servers) { hlprintf_int("You must configure a server before you can send text anywhere!"); return; } if (! to) { hlprintf_int("You must set a destination before you can send text anywhere!"); return; } if (len < 1) return; for (t=to;t;t=t->link) { dl = strlen(t->dest); (*t->on->ops->send)(t->on,t->dest,dl,text,len,action?SENDF_ACTION:0); if (dl && (*t->on->ops->channeldest)(t->on,t->dest,dl)) { if (listening_to(t->on,t->dest,dl)) { ohl_append_channel(t->on,t->on->nick.text,t->on->nick.len,t->on->nick.text,t->on->nick.len,t->dest,dl,text,len,action?OHLF_ACTION:0); } else { ohl_append_tochan(t->on,t->dest,dl,text,len,action?OHLF_ACTION:0); } } else { ohl_append_topriv(t->on,t->dest,dl,text,len,action?OHLF_ACTION:0); } } } static void server_list(const char *arg, int arglen) { SERVER *s; FILE *f; for (s=config.servers;s;s=s->link) { if (!arglen || ((arglen == s->tag.len) && !bcmp(arg,s->tag.text,arglen)) || (!strncmp(arg,s->host,arglen) && !s->host[arglen])) { f = hline_open(0); fprintf(f,"Server %s",s->host); if (s->port) fprintf(f,"/%s",s->port); if (s->tag.len > 0) fprintf(f," (%.*s)",s->tag.len,s->tag.text); fprintf(f," nick %.*s",s->nick.len,s->nick.text); if (s->s < 0) { fprintf(f," not connected"); } else { fprintf(f," fd %d at %s",s->s,s->ctext); } fclose(f); } } } static void server_del(const char *name, int namelen) { SERVER *s; SERVER **sp; sp = &config.servers; while ((s = *sp)) { if (((namelen == s->tag.len) && !bcmp(name,s->tag.text,namelen)) || (!strncmp(name,s->host,namelen) && !s->host[namelen])) { server_dead(s); *sp = s->link; return; } else { sp = &s->link; } } hlprintf_int("No server found matching `%.*s'",namelen,name); } static void server_add(CSTR tag, CSTR host, CSTR nick) { SERVER *s; const char *slash; char *hstr; char *pstr; if (tag.len > 0) { for (s=config.servers;s;s=s->link) { if ((tag.len == s->tag.len) && !bcmp(tag.text,s->tag.text,tag.len)) { hlprintf_int("Duplicate tag"); return; } if (!strncmp(s->host,tag.text,tag.len) && !s->host[tag.len]) { hlprintf_int("Tag duplicates existing server host"); return; } } } slash = memchr(host.text,'/',host.len); if (slash) { hstr = blk_to_nt(host.text,slash-host.text); pstr = blk_to_nt(slash+1,host.len-((slash+1)-host.text)); } else { hstr = blk_to_nt(host.text,host.len); pstr = 0; } do <"ok"> { do <"bad"> { for (s=config.servers;s;s=s->link) { if (!strcmp(hstr,s->host) && !tag.len) { hlprintf_int("Host duplicates existing host, and no tag given"); break <"bad">; } if ((s->tag.len > 0) && !strncmp(hstr,s->tag.text,s->tag.len) && !hstr[s->tag.len]) { hlprintf_int("Host duplicates existing tag"); break <"bad">; } } break <"ok">; } while (0); free(hstr); free(pstr); return; } while (0); s = new_server(); s->stext = blk_to_nt(host.text,host.len); s->tag = tag.len ? wstr_copy_cstr(tag) : NO_WSTR; s->host = hstr; s->port = pstr; s->nick = wstr_copy_cstr(nick); server_noids(s); s->bid = aio_add_block(&start_connection,s); s->link = config.servers; config.servers = s; } static void server_open(const char *name, int namelen) { SERVER *s; for (s=config.servers;s;s=s->link) { if (((namelen == s->tag.len) && !bcmp(name,s->tag.text,namelen)) || (!strncmp(name,s->host,namelen) && !s->host[namelen])) { if (s->s >= 0) { hlprintf_int("Server is already connected"); return; } if (s->scip) { hlprintf_int("Server is in the process of connecting"); return; } server_noids(s); s->bid = aio_add_block(&start_connection,s); } } } static void server_close(const char *name, int namelen) { SERVER *s; for (s=config.servers;s;s=s->link) { if (((namelen == s->tag.len) && !bcmp(name,s->tag.text,namelen)) || (!strncmp(name,s->host,namelen) && !s->host[namelen])) { if (s->s >= 0) { server_dead(s); } else if (s->scip) { hlprintf_int("Aborting connection attempt"); scip_abort(s->scip); s->scip = 0; } else { hlprintf_int("Not connected"); } return; } } } static void slash_join(const char *rest, int restlen) { if (noserver("join a channel")) return; if (! (*onserver->ops->okchan)(onserver,rest,restlen)) { hlprintf_int("%.*s: not a valid channel name",restlen,rest); return; } (*onserver->ops->sendjoin)(onserver,rest,restlen); } static void reset_nexttick(TSTAMP ts) { if (!noseconds && ((last_input > ts) || (ts-last_input < (15*60*TS_SECONDS)))) { nexttick = ((ts / TS_SECONDS) * TS_SECONDS) + TS_SECONDS; clock_show_seconds = 1; } else { nexttick = ((ts / (60*TS_SECONDS)) * (60*TS_SECONDS)) + (60*TS_SECONDS); clock_show_seconds = 0; } nexttick += TS_SECONDS / 100; } static void slash_idle(void) { last_input = 0; reset_nexttick(0); } static void slash_nosec(int nosec) { noseconds = nosec; reset_nexttick(0); } static void slash_part(const char *rest, int restlen) { if (noserver("leave a channel")) return; if (! (*onserver->ops->okchan)(onserver,rest,restlen)) { hlprintf_int("%.*s: not a valid channel name",restlen,rest); return; } (*onserver->ops->sendpart)(onserver,rest,restlen); } #define slash_leave slash_part static void slash_nick(const char *rest, int restlen) { if (noserver("change nicks")) return; if (! (*onserver->ops->oknick)(onserver,rest,restlen)) { hlprintf_int("Not acceptable as a nick"); return; } (*onserver->ops->setnick)(onserver,rest,restlen); } static void slash_to(const char *rest, int restlen) { TO *list; TO **listt; TO *t; int i; int i0; int ic; int errs; SERVER *s; TO *tofree; if (! config.servers) { hlprintf_int("You must configure a server before you can set a text destination!"); return; } listt = &list; i = 0; errs = 0; while (1) { for (;(i= restlen) break; i0 = i; ic = -1; for (;(i= 0) { if (ic == i-1) { hlprintf_int("Missing destination in `%.*s'",i-i0,rest+i0); errs = 1; } if (ic == i0) { if (config.servers->link) { hlprintf_int("Can't use empty tag with multiple servers"); errs = 1; } s = config.servers; } else { do <"found"> { for (s=config.servers;s;s=s->link) if ((s->tag.len == ic-i0) && !bcmp(s->tag.text,rest+i0,ic-i0)) break <"found">; hlprintf_int("Server tag `%.*s' not found",ic-i0,rest+i0); errs = 1; } while (0); } i0 = ic + 1; } else { if (config.servers->link) { hlprintf_int("Must specify a tag when using multiple servers"); errs = 1; } else { s = config.servers; } } if (! errs) { t = malloc(sizeof(TO)); t->on = s; t->dest = blk_to_nt(rest+i0,i-i0); *listt = t; listt = &t->link; } } *listt = 0; if (errs) { tofree = list; } else { tofree = to; to = list; } while ((t = tofree)) { tofree = t->link; free(t->dest); free(t); } want_redraw |= WR_MODELINE; } static void slash_msg(const char *rest, int restlen) { int d0; int dl; int i; if (noserver("send a /msg")) return; for (i=0;(iops->send)(onserver,rest+d0,dl,rest+i,restlen-i,0); if ((*onserver->ops->channeldest)(onserver,rest+d0,dl)) { ohl_append_tochan(onserver,rest+d0,dl,rest+i,restlen-i,0); } else { ohl_append_topriv(onserver,rest+d0,dl,rest+i,restlen-i,0); } } static void slash_debug(const char *rest, int restlen) { char *t; t = malloc(restlen+1); bcopy(rest,t,restlen); t[restlen] = '\0'; debug = atoi(t); free(t); } static void slash_on(const char *rest, int restlen) { int i; int s0; int sl; SERVER *s; for (i=0;(i { for (s=config.servers;s;s=s->link) if ((s->tag.len == sl) && !bcmp(s->tag.text,rest+s0,sl)) break <"found">; hlprintf_int("/on tag `%.*s' not found",sl,rest+s0); return; } while (0); onserver = s; slash_command(rest+i,restlen-i); } static void slash_server(const char *rest, int restlen) { int i; int s0; int sl; void nextword(void) { for (;(i all servers)"); hlprintf_int("add [TAG:] HOST NICK"); hlprintf_int(" add a new server at HOST, tag TAG, nick NICK"); hlprintf_int("del TAG-or-HOST"); hlprintf_int(" forget server with the given TAG or HOST"); hlprintf_int("close TAG-or-HOST"); hlprintf_int(" close server connection, but remember server"); hlprintf_int("open TAG-or-HOST"); hlprintf_int(" try to (re)open a closed server connection"); return; } break; case 3: switch (rest[s0]) { case 'a': if (!bcmp(rest+s0,"add",3)) { CSTR tag; CSTR host; CSTR nick; nextword(); if ((sl > 0) && (rest[s0+sl-1] == ':')) { tag = (CSTR){ .text = rest + s0, .len = sl - 1 }; hlprintf_int("tag %.*s",tag.len,tag.text); nextword(); } else { tag = NO_CSTR; hlprintf_int("no tag"); } host = (CSTR){ .text = rest + s0, .len = sl }; hlprintf_int("host %.*s",host.len,host.text); nextword(); if (! sl) { hlprintf_int("/server add: not enough args (args: [tag:] host nick)"); return; } nick = (CSTR){ .text = rest + s0, .len = sl }; hlprintf_int("nick %.*s",nick.len,nick.text); nextword(); if (sl) { hlprintf_int("extra %.*s",sl,rest+s0); hlprintf_int("/server add: too many args (args: [tag:] host nick)"); return; } server_add(tag,host,nick); return; } break; case 'd': if (!bcmp(rest+s0,"del",3)) { while (1) { nextword(); if (sl == 0) break; server_del(rest+s0,sl); } return; } break; } break; case 4: switch (rest[s0]) { case 'l': if (!bcmp(rest+s0,"list",4)) { nextword(); if (sl) { while (sl) { server_list(rest+s0,sl); nextword(); } } else { server_list(0,0); } return; } break; case 'o': if (!bcmp(rest+s0,"open",4)) { while (1) { nextword(); if (sl < 1) break; server_open(rest+s0,sl); } return; } break; } break; case 5: if (!bcmp(rest+s0,"close",5)) { while (1) { nextword(); if (sl < 1) break; server_close(rest+s0,sl); } return; } break; } hlprintf_int("/server keyword `%.*s' unrecognized (\"/server ?\" for help)",sl,rest+s0); } static void report_histshow(int inx) { hlprintf_int("%s (%s): %s",histshow_bits[inx].name,histshow_bits[inx].text,(histshow&histshow_bits[inx].bit)?"shown":"hidden"); } static void dump_histshow(void) { int i; for (i=0;histshow_bits[i].name;i++) { report_histshow(i); } } static void slash_show_hide(const char *rest, int restlen, void (*procbit)(unsigned int)) { int i; int k0; int kl; int j; unsigned int oldhs; for (i=0;(i= restlen) { dump_histshow(); return; } oldhs = histshow; while <"args"> (1) { for (;(i= restlen) break; k0 = i; for (;(i; } } hlprintf_int("%.*s: not recognized",kl,rest+k0); } if (histshow != oldhs) rerender_hist(); } static void histshow_show(unsigned int bit) { histshow |= bit; } static void histshow_hide(unsigned int bit) { histshow &= ~bit; } static void slash_wrhist(void) { WRHIST *h; FILE *f; const char *pre; unsigned int v; void bit_(unsigned int b, const char *t) #define bit(x) bit_((x),#x) { if (v & b) { fprintf(f,"%s%s",pre,t); pre = " | "; v &= ~b; } } for (h=wrhist;h;h=h->link) { f = hline_open(0); v = h->value; fprintf(f,"%8u %08x < ",h->count,v); pre = ""; bit(WR_HIST); bit(WR_INPUT); bit(WR_WINCH); bit(WR_CLOCK); bit(WR_BEEP); bit(WR_MODELINE); if (v) fprintf(f,"%s%08x",pre,v); fprintf(f," >"); fclose(f); } #undef bit } static void slash_away(const char *cmd, int len) { int i; if (noserver("/away")) return; for (i=0;(iops->sendaway)(onserver,cmd+i,len-i); } static void slash_command(const char *cmd, int len) { int c0; int cl; int i; for (i=0;(iops->slashcmd)(onserver,cmd,len,c0,cl,i)) return; hlprintf_int("Unrecognized command /%.*s",cl,cmd+c0); } static void ihist_save(void) { IHLINE *l; l = malloc(sizeof(IHLINE)+es_len(&ie->line)); bcopy(es_buf(&ie->line),l+1,es_len(&ie->line)); l->text.text = (void *) (l + 1); l->text.len = es_len(&ie->line); l->nlink = 0; l->olink = ie->ihist.newest; ie->ihist.newest = l; if (l->olink) l->olink->nlink = l; else ie->ihist.oldest = l; ie->ihist.size += l->text.len; while (ie->ihist.size > DEFAULT_MAX_IHISTORY) { l = ie->ihist.oldest; ie->ihist.oldest = l->nlink; ie->ihist.size -= l->text.len; free(l); } } static void ie_iline(void) { ihist_save(); if (ie == &ie_normal) { plain_text(es_buf(&ie->line),es_len(&ie->line),0); } else if (ie == &ie_colon) { plain_text(es_buf(&ie->line),es_len(&ie->line),1); } else if (ie == &ie_slash) { onserver = (config.servers && !config.servers->link) ? config.servers : 0; slash_command(es_buf(&ie->line),es_len(&ie->line)); } es_clear(&ie->line); ie->curs = 0; ie = &ie_normal; } static void ie_del(int at, int n) { char *b; int l; b = es_buf(&ie->line); l = es_len(&ie->line); if ((at < 0) || (n < 0) || (at+n < 0) || (at+n > l)) abort(); if (n < 1) return; if (ie->mark > at) { if (ie->mark <= at+n) { ie->mark = at; } else { ie->mark -= n; } } if (at+n < l) bcopy(b+at+n,b+at,l-(at+n)); es_pop_n(&ie->line,n); } static void ie_del_chain(int at, int n) { char *t; KILLED *k; do { if (ie->icookie == &icookie_delete) { if (at == del_at) { t = malloc(del_n+n); bcopy(killring.newest->text.text,t,del_n); bcopy(es_buf(&ie->line)+at,t+del_n,n); del_n += n; free(killring.newest->text.text); killring.newest->text.text = t; killring.newest->text.len = del_n; break; } else if (at+n == del_at) { t = malloc(del_n+n); bcopy(es_buf(&ie->line)+at,t,n); bcopy(killring.newest->text.text,t+n,del_n); del_at -= n; del_n += n; free(killring.newest->text.text); killring.newest->text.text = t; killring.newest->text.len = del_n; break; } } k = malloc(sizeof(KILLED)); k->text = wstr_copy_pl(es_buf(&ie->line)+at,n); if (killring.newest && (killring.newest->text.len == k->text.len) && !bcmp(killring.newest->text.text,k->text.text,k->text.len)) { free(k->text.text); free(k); } else { k->newer = 0; k->older = killring.newest; if (k->older) k->older->newer = k; else killring.oldest = k; killring.newest = k; del_at = at; del_n = n; killring.n ++; while (killring.n > KILLRING_MAX_N) { k = killring.oldest; k->newer->older = 0; killring.oldest = k->newer; free(k->text.text); free(k); killring.n --; } } } while (0); ie_del(at,n); ie->nexticookie = &icookie_delete; } static void ie_ins(int at, const char *text, int len) { char *b; int l; l = es_len(&ie->line); if ((at < 0) || (at > l)) { abort(); } else if (at == l) { es_append_n(&ie->line,text,len); } else { es_append_n(&ie->line,text,len); b = es_buf(&ie->line); if (at < l) bcopy(b+at,b+at+len,l-at); bcopy(text,b+at,len); } if (at < ie->mark) ie->mark += len; } static void ring_yank(int simple) { if (! killring.newest) { ui_beep(); return; } ie->nexticookie = &icookie_yank; if (ie->icookie == &icookie_yank) { if (simple) { ie_ins(ie->curs,yank_k->text.text,yank_k->text.len); ie->curs += yank_k->text.len; yank_n += yank_k->text.len; return; } ie_del(ie->curs-yank_k->text.len,yank_k->text.len); ie->curs -= yank_k->text.len; yank_k = yank_k->older; if (! yank_k) { ui_beep(); ie->nexticookie = 0; return; } } else { yank_k = killring.newest; } yank_at = ie->curs; yank_n = yank_k->text.len; ie_ins(ie->curs,yank_k->text.text,yank_n); ie->curs += yank_n; } static void ie_transpose(void) { char *b; char t; if (ie->curs < 2) abort(); b = es_buf(&ie->line); t = b[ie->curs-1]; b[ie->curs-1] = b[ie->curs-2]; b[ie->curs-2] = t; } static void ie_self_insert(char c) { ie_ins(ie->curs,&c,1); ie->curs ++; } static int wchar(unsigned char c) { switch (c) { case LETTERS: case DIGITS: case 192: case 193: case 194: case 195: case 196: case 197: case 198: case 199: case 200: case 201: case 202: case 203: case 204: case 205: case 206: case 207: case 208: case 209: case 210: case 211: case 212: case 213: case 214: case 216: case 217: case 218: case 219: case 220: case 221: case 222: case 223: case 224: case 225: case 226: case 227: case 228: case 229: case 230: case 231: case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239: case 240: case 241: case 242: case 243: case 244: case 245: case 246: case 248: case 249: case 250: case 251: case 252: case 253: case 254: case 255: return(1); break; } return(0); } static void ie_move_b_word(void) { char *b; int c; c = ie->curs; if (c < 1) return; b = es_buf(&ie->line); do c --; while ((c > 0) && (wchar(b[c-1]) || !wchar(b[c]))); ie->curs = c; } static void ie_move_f_word(void) { char *b; int l; int c; c = ie->curs; l = es_len(&ie->line); if (c >= l) return; b = es_buf(&ie->line); do c ++; while ((c < l) && (wchar(b[c]) || !wchar(b[c-1]))); ie->curs = c; } static void ie_del_b_word(void) { int old; old = ie->curs; ie_move_b_word(); ie_del_chain(ie->curs,old-ie->curs); } static void ie_del_f_word(void) { int old; old = ie->curs; ie_move_f_word(); ie_del_chain(old,ie->curs-old); ie->curs = old; } static void ie_wipe_histrecall(void) { if (ie->hcurs) { ie_del(ie->curs-ie->hcurs->text.len,ie->hcurs->text.len); ie->curs -= ie->hcurs->text.len; } else { ui_beep(); } } static void ie_control_w(void) { int t; if (ie->icookie == &icookie_hist_n_p) { ie_wipe_histrecall(); return; } if (ie->mark >= 0) { if (ie->mark < ie->curs) { t = ie->curs; ie->curs = ie->mark; ie->mark = t; } ie_del_chain(ie->curs,ie->mark-ie->curs); ie->mark = -1; } else { ie_del_b_word(); } } static void ie_ihist_newer(void) { if (ie->icookie == &icookie_hist_n_p) { ie_wipe_histrecall(); } else { ie->hcurs = 0; ui_beep(); } ie->hcurs = ie->hcurs ? ie->hcurs->nlink : ie->ihist.oldest; if (ie->hcurs) { ie_ins(ie->curs,ie->hcurs->text.text,ie->hcurs->text.len); ie->curs += ie->hcurs->text.len; } ie->nexticookie = &icookie_hist_n_p; } static void ie_ihist_older(void) { if (ie->icookie == &icookie_hist_n_p) { ie_wipe_histrecall(); } else { ie->hcurs = 0; } ie->hcurs = ie->hcurs ? ie->hcurs->olink : ie->ihist.newest; if (ie->hcurs) { ie_ins(ie->curs,ie->hcurs->text.text,ie->hcurs->text.len); ie->curs += ie->hcurs->text.len; } ie->nexticookie = &icookie_hist_n_p; } static void switch_editor(EDIT *to) { if (ie == to) { ie = &ie_normal; } else { ie = to; } } static void append_zeros(ES *e, int n) { for (;n>0;n--) es_append_1(e,0); } static void append_with_zeros(ES *t, ES *h, const char *d, int l) { es_append_n(t,d,l); append_zeros(h,l); } static void append_parallel(ES *t, ES *h, CSTRH cs) { int i; es_append_n(t,cs.text,cs.len); if (cs.hl) { for (i=0;iflags & OHLF_HIDDEN) && !(histshow & HS_HIDDEN) ); } static void ohl_compute_dl(OHLINE *hl) { SERVER *s; const char *dtext; const char *dhl; int dtl; int i; int j; int n; int o; int l; CSTRH *ls; int max; ONCHAN *oc; static ES te = ES_STATIC_INIT; static ES th = ES_STATIC_INIT; time_t tt; struct tm *tm; unsigned int f; if (hl->dlc == ui_cols()) return; free(hl->dtext); free(hl->dfree); if (hl->kind == OHLK_NONE) abort(); if ((hl->kind == OHLK_INTERNAL) && (histshow == 0)) { hl->dfree = 0; dtext = hl->body.text; dhl = hl->body.hl; dtl = hl->body.len; } else { es_clear(&te); es_clear(&th); if (histshow & HS_FTS) { char txt[23]; tt = hl->time / 1000000ULL; tm = localtime(&tt); i = sprintf(&txt[0],"%04d-%02d-%02d %02d:%02d:%02d.%02d ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, ((int)(hl->time % 1000000ULL)) / 10000); append_with_zeros(&te,&th,&txt[0],i); } else if (histshow & HS_TS) { char txt[9]; tt = hl->time / 1000000ULL; tm = localtime(&tt); switch (hl->age) { case AGE_60SEC: i = sprintf(&txt[0],"%02d.%05d",tm->tm_sec,((int)(hl->time%1000000ULL))/10); break; case AGE_30MIN: i = sprintf(&txt[0],"%02d:%02d.%02d",tm->tm_min,tm->tm_sec,((int)(hl->time%1000000ULL))/10000); break; case AGE_24HOUR: i = sprintf(&txt[0],"%02d:%02d:%02d",tm->tm_hour,tm->tm_min,tm->tm_sec); break; case AGE_7DAY: i = sprintf(&txt[0],"%02d %02d:%02d",tm->tm_mday,tm->tm_hour,tm->tm_min); break; case AGE_6MON: i = sprintf(&txt[0],"%02d-%02d %02d",tm->tm_mon+1,tm->tm_mday,tm->tm_hour); break; case AGE_OLD: i = sprintf(&txt[0],"%02d-%02d-%02d",tm->tm_year%100,tm->tm_mon+1,tm->tm_mday); break; default: abort(); break; } if (i != sizeof(txt)-1) abort(); append_with_zeros(&te,&th,&txt[0],i); append_with_zeros(&te,&th," ",1); } if (histshow & HS_LT) { switch (hl->kind) { default: abort(); break; case OHLK_INTERNAL: append_with_zeros(&te,&th,"IN ",3); break; case OHLK_SERVER: append_with_zeros(&te,&th,"SE ",3); break; case OHLK_CHANNEL: append_with_zeros(&te,&th,"CH ",3); break; case OHLK_PRIVATE: append_with_zeros(&te,&th,"PR ",3); break; case OHLK_TOCHAN: append_with_zeros(&te,&th,"TC ",3); break; case OHLK_TOPRIV: append_with_zeros(&te,&th,"TP ",3); break; } } if (histshow & HS_STATS) { i = es_append_printf(&te,"%d,%d ",hl->cc,hl->zws); append_zeros(&th,i); } switch (hl->kind) { default: abort(); break; case OHLK_INTERNAL: s = 0; break; case OHLK_SERVER: s = hl->server.srv; break; case OHLK_CHANNEL: s = hl->channel.srv; break; case OHLK_PRIVATE: s = hl->private.srv; break; case OHLK_TOCHAN: s = hl->tochan.srv; break; case OHLK_TOPRIV: s = hl->topriv.srv; break; } if (s && config.servers && config.servers->link) { append_with_zeros(&te,&th,"[",1); append_with_zeros(&te,&th,s->tag.text,s->tag.len); append_with_zeros(&te,&th,"] ",2); } switch (hl->kind) { default: abort(); break; case OHLK_INTERNAL: break; case OHLK_SERVER: if (hl->server.prefix.len > 0) { if (! ((s->servname.len == hl->server.prefix.len) && !bcmp(s->servname.text,hl->server.prefix.text,s->servname.len))) { append_parallel(&te,&th,hl->server.prefix); append_with_zeros(&te,&th," ",1); } } break; case OHLK_CHANNEL: n = 0; for (oc=listens;(n<2)&&oc;oc=oc->link) if (oc->s == hl->channel.srv) n ++; if (hl->channel.flags & OHLF_ACTION) { append_with_zeros(&te,&th,"* ",2); } else { append_with_zeros(&te,&th,"<",1); } append_parallel(&te,&th,hl->channel.nickwho); if (n > 1) { append_with_zeros(&te,&th,"@",1); append_parallel(&te,&th,hl->channel.chan); } if (! (hl->channel.flags & OHLF_ACTION)) append_with_zeros(&te,&th,">",1); append_with_zeros(&te,&th," ",1); break; case OHLK_PRIVATE: if (hl->private.flags & OHLF_ACTION) { append_with_zeros(&te,&th,"<< ",3); } else { append_with_zeros(&te,&th,"<",1); } append_parallel(&te,&th,hl->private.nickwho); if (! (hl->private.flags & OHLF_ACTION)) append_with_zeros(&te,&th,"<",1); append_with_zeros(&te,&th," ",1); break; case OHLK_TOCHAN: append_with_zeros(&te,&th,">",1); append_parallel(&te,&th,hl->tochan.chan); append_with_zeros(&te,&th,"> ",2); f = hl->tochan.flags; if (0) { case OHLK_TOPRIV: append_with_zeros(&te,&th,">",1); append_parallel(&te,&th,hl->topriv.who); append_with_zeros(&te,&th,"> ",2); f = hl->topriv.flags; } if (f & OHLF_ACTION) { append_with_zeros(&te,&th,"* ",2); append_with_zeros(&te,&th,s->nick.text,s->nick.len); append_with_zeros(&te,&th," ",1); } break; } append_parallel(&te,&th,hl->body); dtl = es_len(&te); if (es_len(&th) != dtl) abort(); hl->dfree = malloc(2*dtl); bcopy(es_buf(&te),hl->dfree,dtl); bcopy(es_buf(&th),hl->dfree+dtl,dtl); dtext = hl->dfree; dhl = hl->dfree + dtl; } hl->dl_ = 0; hl->dtext = 0; o = 0; while (1) { n = dtl - o; if (n < 1) break; hl->dtext = realloc(hl->dtext,(hl->dl_+1)*sizeof(*hl->dtext)); ls = &hl->dtext[hl->dl_++]; ls->text = dtext + o; ls->hl = dhl ? dhl + o : 0; max = ui_cols() - 1; if (o) max -= 2; if (n > max) { j = max / 2; do <"brkpt"> { for (i=max-1;i>=j;i--) { if (dtext[o+i] == ' ') { for (l=i-1;l>0;l--) if (dtext[o+l] != ' ') break; l ++; o += i + 1; break <"brkpt">; } } l = max; o += max; } while (0); ls->len = l; while ((o < dtl) && (dtext[o] == ' ')) o ++; } else { ls->len = n; break; } } hl->dlc = ui_cols(); } static void scrollback_clear_count(void) { sb_countlen = 0; } static unsigned int scrollback_get_count(void) { int v; if (sb_countlen < 1) return(1); es_append_1(&sb_countstr,'\0'); v = atoi(es_buf(&sb_countstr)+es_len(&sb_countstr)-sb_countlen-1); sb_countlen = 0; return(v); } static void scrollback_prepare_count(int c) { char *s; int l; l = asprintf(&s,"%d",c); es_clear(&sb_countstr); es_append_n(&sb_countstr,s,l); sb_countlen = l; free(s); } static void scrollback_done(void) { scrollback_clear_count(); scrollback.line = 0; ie_nextchar = &nextchar_normal; want_redraw |= WR_ALL; } static void scrollback_check_done(void) { int i; OHLINE *l; int lwl; int inhidden; if (! scrollback.line) { scrollback_done(); return; } i = ui_lines() - 2; l = scrollback.line; lwl = scrollback.lwl; i += lwl; inhidden = 0; while (1) { if (! ohl_is_hidden(l)) { ohl_compute_dl(l); i -= l->dl_; inhidden = 0; } else if (! inhidden) { inhidden = 1; i --; } l = l->anlink; if (i < 1) break; if (! l) break; } if (! l) { scrollback_done(); return; } } static void scrollback_page_up(void) { int i; OHLINE *l; int lwl; scrollback_clear_count(); i = ((ui_lines() - 1) * 3) / 4; l = scrollback.line; ohl_compute_dl(l); lwl = scrollback.lwl; if (lwl < 0) { if (l->aolink) abort(); ui_beep(); return; } i -= lwl; while ((i > 0) && l->aolink) { l = l->aolink; if (! ohl_is_hidden(l)) { ohl_compute_dl(l); i -= l->dl_; } else if (l->aolink && !ohl_is_hidden(l->aolink)) { i --; } } scrollback.line = l; scrollback.lwl = - i; scrollback_check_done(); } static void scrollback_line_up(void) { unsigned int n; OHLINE *l; if (ohl_is_hidden(scrollback.line)) { while (1) { l = scrollback.line->aolink; if (!l || !ohl_is_hidden(l)) break; scrollback.line = l; scrollback.lwl = 0; } } for (n=scrollback_get_count();n>0;n--) { l = scrollback.line; if (! ohl_is_hidden(l)) { ohl_compute_dl(l); if (scrollback.lwl >= l->dl_) { scrollback.lwl = l->dl_ - 1; continue; } if (scrollback.lwl > 0) { scrollback.lwl --; continue; } } l = l->aolink; if (! l) { if (scrollback.lwl <= -(ui_lines()-2)) break; scrollback.lwl --; continue; } if (ohl_is_hidden(l)) { while (l->aolink && ohl_is_hidden(l->aolink)) l = l->aolink; scrollback.line = l; scrollback.lwl = 0; } else { scrollback.line = l; ohl_compute_dl(l); scrollback.lwl = l->dl_ - 1; } } scrollback_check_done(); } static void scrollback_page_down(void) { int i; OHLINE *l; int lwl; scrollback_clear_count(); i = ((ui_lines() - 1) * 3) / 4; l = scrollback.line; if (ohl_is_hidden(l)) { while (l->anlink && ohl_is_hidden(l->anlink)) l = l->anlink; } else { ohl_compute_dl(l); lwl = scrollback.lwl; i += lwl; if (l->dl_ >= i) { scrollback.lwl = i; return; } } while (1) { if (! ohl_is_hidden(l)) { ohl_compute_dl(l); i -= l->dl_; } else if (!l->anlink || !ohl_is_hidden(l->anlink)) { i --; } l = l->anlink; if (i < 1) break; if (! l) break; } if (! l) { scrollback_done(); return; } scrollback.line = l; scrollback.lwl = ohl_is_hidden(l) ? 0 : l->dl_ + i; scrollback_check_done(); } static void scrollback_line_down(void) { unsigned int n; for (n=scrollback_get_count();n>0;n--) { if (ohl_is_hidden(scrollback.line)) { while (scrollback.line->anlink && ohl_is_hidden(scrollback.line->anlink)) scrollback.line = scrollback.line->anlink; } else { ohl_compute_dl(scrollback.line); scrollback.lwl ++; if (scrollback.lwl < scrollback.line->dl_) continue; } scrollback.line = scrollback.line->anlink; scrollback.lwl = 0; if (! scrollback.line) break; } scrollback_check_done(); } static void scrollback_top(void) { scrollback_clear_count(); scrollback.line = ohist.oldest; scrollback.lwl = 0; } static void scrollback_down_10(void) { if (sb_countlen < 1) scrollback_prepare_count(10); scrollback_line_down(); } static void scrollback_up_10(void) { if (sb_countlen < 1) scrollback_prepare_count(10); scrollback_line_up(); } static int scrollback_start(void) { int i; OHLINE *l; want_redraw |= WR_ALL; // Compute initial top-of-screen (assuming no/short input line) l = ohist.newest; if (! l) { // No history? Can't do much. ui_beep(); return(0); } i = ui_lines() - 2; while (1) { if (ohl_is_hidden(l)) { while (l->aolink && ohl_is_hidden(l->aolink)) l = l->aolink; i --; } else { ohl_compute_dl(l); i -= l->dl_; } if (i < 1) break; if (! l->aolink) break; l = l->aolink; } scrollback.line = l; scrollback.lwl = - i; ie_nextchar = &nextchar_viewsb; sbserial = outserial; sblastout = outserial - 1; return(1); } static void scrollback_count_char(unsigned char c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (sb_countlen == 0) { es_clear(&sb_countstr); es_append_n(&sb_countstr,"Count: ",7); } es_append_1(&sb_countstr,c); sb_countlen ++; break; case 8: case 127: if (sb_countlen > 0) { es_pop_1(&sb_countstr); sb_countlen --; } break; } } static void il_input(unsigned char c) { ie->icookie = ie->nexticookie; ie->nexticookie = 0; (*ie_nextchar)(c); } static void scrollback_change_linetypes(void) { histshow ^= HS_LT; rerender_hist(); } static void scrollback_change_timestamps(void) { switch (histshow & (HS_TS|HS_FTS)) { case 0: histshow |= HS_TS; break; case HS_TS: histshow = (histshow & ~HS_TS) | HS_FTS; break; default: histshow &= ~(HS_TS | HS_FTS); break; } rerender_hist(); } static void nextchar_viewsb_esc(unsigned char c) { unsigned int wrbit; wrbit = WR_HIST; ie_nextchar = &nextchar_viewsb; switch (c) { case 12: // ^L scrollback_change_linetypes(); break; case 20: // ^T scrollback_change_timestamps(); break; case '<': scrollback_top(); break; case '>': scrollback_done(); break; case 'n': scrollback_page_down(); break; case 'p': scrollback_page_up(); break; default: wrbit = WR_BEEP; ui_beep(); break; } want_redraw |= wrbit; } static void nextchar_viewsb(unsigned char c) { unsigned int wrbit; wrbit = WR_HIST; switch (c) { case 27: // ESC ie->nexticookie = ie->icookie; ie_nextchar = &nextchar_viewsb_esc; wrbit = 0; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 8: case 127: wrbit = WR_INPUT; scrollback_count_char(c); break; case ' ': scrollback_page_down(); break; case 'b': scrollback_page_up(); break; case 'd': scrollback_down_10(); break; case 'u': scrollback_up_10(); break; case 'j': case 'n': scrollback_line_down(); break; case 'k': case 'p': scrollback_line_up(); break; default: wrbit = WR_BEEP; ui_beep(); break; } want_redraw |= wrbit; } static void nextchar_normal_esc(unsigned char c) { unsigned int wrbit; wrbit = WR_INPUT; ie_nextchar = &nextchar_normal; switch (c) { case 0: // ^@ ie->mark = -1; break; case '/': ie->nexticookie = ie->icookie; switch_editor(&ie_slash); break; case ':': ie->nexticookie = ie->icookie; switch_editor(&ie_colon); break; case '<': if (scrollback_start()) scrollback_top(); break; case 'b': ie_move_b_word(); break; case 'd': ie_del_f_word(); break; case 'f': ie_move_f_word(); break; case 'h': ie_del_b_word(); break; case 'p': if (scrollback_start()) scrollback_page_up(); break; case 'y': ring_yank(0); break; default: wrbit = WR_BEEP; ui_beep(); break; } want_redraw |= wrbit; } static void nextchar_normal(unsigned char c) { unsigned int wrbit; wrbit = WR_INPUT; switch (c) { case 0: // ^@ ie->mark = ie->curs; break; case 1: // ^A ie->curs = 0; break; case 2: // ^B if (ie->curs > 0) ie->curs --; break; case 4: // ^D if (ie->curs < es_len(&ie->line)) ie_del_chain(ie->curs,1); break; case 5: // ^E ie->curs = es_len(&ie->line); break; case 6: // ^F if (ie->curs < es_len(&ie->line)) ie->curs ++; break; case 8: // ^H case 127: // DEL if (ie->curs > 0) { ie->curs --; ie_del_chain(ie->curs,1); } break; case 10: // ^J case 13: // ^M ie_iline(); break; case 11: // ^K; ie_del_chain(ie->curs,es_len(&ie->line)-ie->curs); break; case 14: // ^N ie_ihist_newer(); break; case 16: // ^P ie_ihist_older(); break; case 20: // ^T if (ie->curs >= 2) ie_transpose(); break; case 21: // ^U case 24: // ^X ie->curs = 0; ie_del_chain(0,es_len(&ie->line)); break; case 23: // ^W ie_control_w(); break; case 25: // ^Y ring_yank(1); break; case 27: // ESC ie->nexticookie = ie->icookie; ie_nextchar = &nextchar_normal_esc; wrbit = 0; break; case ' ' ... '~': case 160 ... 255: ie_self_insert(c); break; default: wrbit = WR_BEEP; ui_beep(); break; } want_redraw |= wrbit; } static void got_input(void) { last_input = ts_now(); reset_nexttick(last_input); } static void rd_in(void *arg __attribute__((__unused__))) { unsigned char rbuf[512]; int nr; int i; nr = read(0,&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"\r\n%s: input read: %s\r\n",__progname,strerror(errno)); exit(1); } got_input(); for (i=0;ipw_name); return; } fprintf(stderr,"%s: can't work out your login name - maybe use -login?\n",__progname); exit(1); } static void get_fullname(void) { struct passwd *pw; char *comma; int l; if (fullname) return; pw = getpwnam(login); if (! pw) pw = getpwuid(getuid()); if (pw) { comma = index(pw->pw_gecos,','); if (comma) { l = comma - pw->pw_gecos; fullname = malloc(l+1); bcopy(pw->pw_gecos,fullname,l); fullname[l] = '\0'; } else { fullname = strdup(pw->pw_gecos); } return; } fprintf(stderr,"%s: can't work out your full name - maybe use -name?\n",__progname); exit(1); } static void config_err(CONFIG *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void config_err(CONFIG *c, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s: %s, line %d: %s\n",__progname,c->file,c->line,s); free(s); c->errs = 1; } static void free_server(SERVER *s) { int i; free(s->stext); free(s->tag.text); free(s->host); free(s->port); free(s->nick.text); for (i=s->nchan-1;i>=0;i--) free(s->chans[i]); free(s->chans); free(s->ctext); aio_oq_flush(&s->oq); server_noids(s); if (s->ops) (*s->ops->done)(s); free(s->servname.text); if (s->scip) abort(); free(s); } static void config_line_server(CONFIG *c, const char *text, int len) { int i; SERVER *s; int s0; const char *slash; s = new_server(); for (i=0;(i= len) { config_err(c,"`server' line has no server name"); free_server(s); return; } s0 = i; for (;(iops = lookup_protocol_ops(text+s0,i-s0); if (! s->ops) { config_err(c,"`server' line has unrecognized protocol `%.*s'",i-s0,text+s0); free_server(s); return; } (*s->ops->init)(s); for (;(i s0) && (text[i-1] == ':')) { s->tag.len = i - 1 - s0; s->tag.text = malloc(s->tag.len); bcopy(&text[s0],s->tag.text,s->tag.len); for (;(i= len) { config_err(c,"`server' line has no server name"); free_server(s); return; } s0 = i; for (;(ihost = blk_to_nt(&text[s0],slash-&text[s0]); s->port = blk_to_nt(slash+1,&text[i]-(slash+1)); asprintf(&s->stext,"%s/%s",s->host,s->port); } else { s->host = blk_to_nt(&text[s0],i-s0); s->stext = strdup(s->host); } if (! s->tag.len) s->tag = wstr_copy_nt(s->host); for (;(i= len) { config_err(c,"`server' line has no nick"); free_server(s); return; } s0 = i; for (;(iops->oknick)(s,&text[s0],i-s0)) config_err(c,"`server' line has invalid nick `%.*s'",i-s0,&text[s0]); s->nick = wstr_copy_pl(&text[s0],i-s0); while (1) { for (;(i= len) break; s0 = i; for (;(iops->okchan)(s,&text[s0],i-s0)) config_err(c,"`server' line has invalid channel name `%.*s'",i-s0,&text[s0]); s->chans = realloc(s->chans,(s->nchan+1)*sizeof(*s->chans)); s->chans[s->nchan++] = blk_to_nt(&text[s0],i-s0); } s->link = c->servers; c->servers = s; } static void config_line(CONFIG *c, const char *text, int len) { int i; const char *k0; int kl; for (i=0;(i= len) || (text[i] == '#') || (text[i] == ';')) return; k0 = &text[i]; for (;(ilink = 0; config.servers->tag = wstr_copy_nt(tag?:host); config.servers->host = strdup(host); if (port) { asprintf(&config.servers->stext,"%s/%s",host,port); config.servers->port = strdup(port); } else { config.servers->stext = strdup(host); config.servers->port = 0; } config.servers->nick = wstr_copy_nt(nick?:login); config.servers->nchan = njoins; config.servers->chans = joins; if (protocol) { config.servers->ops = lookup_protocol_ops(protocol,strlen(protocol)); if (! config.servers->ops) { fprintf(stderr,"%s: unrecognized protocol `%s'\n",__progname,protocol); exit(1); } } else { config.servers->ops = &ops_irc; } (*config.servers->ops->init)(config.servers); return; } if (! conffile) { char *home; home = getenv("HOME"); if (! home) { fprintf(stderr,"%s: no -config and no $HOME\n",__progname); exit(1); } asprintf(&conffile,"%s/.chcrc",home); } config.servers = 0; f = fopen(conffile,"r"); if (! f) { config.file = ""; config.line = 0; return; } config.file = conffile; es_init(&l); config.line = 1; while <"read"> (1) { c = getc(f); switch (c) { case '\n': config_line(&config,es_buf(&l),es_len(&l)); es_clear(&l); config.line ++; break; case EOF: if (es_len(&l)) config_err(&config,"file ends partway through a line"); break <"read">; default: es_append_1(&l,c); break; } } fclose(f); es_done(&l); if (config.errs) exit(1); } static void add_cstrh(const CSTRH *s) { int i; int i0; int hl; hl = 0; i0 = -1; for (i=0;ilen;i++) { if (s->hl && s->hl[i]) { if (! hl) { if (i0 >= 0) { ui_addnstr(&s->text[i0],i-i0); i0 = -1; } } if (i0 < 0) { i0 = i; hl = 1; } } else { if (hl) { if (i0 >= 0) { ui_standout(); ui_addnstr(&s->text[i0],i-i0); ui_standend(); i0 = -1; } } if (i0 < 0) { i0 = i; hl = 0; } } } if (i0 >= 0) { if (hl) ui_standout(); ui_addnstr(&s->text[i0],i-i0); if (hl) ui_standend(); } } static void add_hidden_disp(int n) { if (n < 8) { ui_addnstr(".......",n); } else { char *nbuf; int w; w = asprintf(&nbuf,"...%d",n); ui_addnstr(nbuf,w); free(nbuf); } } static XY redraw_normal(void) { int i; OHLINE *h; int ill; int y; int ilo; int cp; int cl; int cy; int cx; int n; int pl; int ll; int tl; int l; TO *t; int x; int nhidden; pl = es_len(&ie->pref); ll = es_len(&ie->line); tl = pl + ll; if (ie->mark >= 0) tl ++; // How many lines the input editor needs. ill = (tl / ui_cols()) + 1; if (ill != lastilines) want_redraw |= WR_HIST|WR_INPUT|WR_MODELINE; // The highest Y value occupied by history. y = ui_lines() - ill - 2; // If there's room for any history lines and we want to redraw them... if ((y >= 0) && (want_redraw & WR_HIST)) { // ...lay them out... h = ohist.newest; while (1) { if (! h) break; if (ohl_is_hidden(h)) { h->y = y; if (!h->aolink || !ohl_is_hidden(h->aolink)) y --; } else { ohl_compute_dl(h); y -= h->dl_; h->y = y + 1; } if (y < 0) break; h = h->aolink; } // ...and draw them. if (! h) h = ohist.oldest; ui_standend(); y = h ? h->y : ui_lines()-ill; for (i=0;ianlink) { if (ohl_is_hidden(h)) { nhidden ++; if (!h->anlink || !ohl_is_hidden(h->anlink)) { ui_move(h->y,0); add_hidden_disp(nhidden); ui_clrtoeol(); nhidden = 0; } } else { for (i=0;idl_;i++) { if (h->y+i < 0) continue; ui_move(h->y+i,0); if (i) ui_addstr(" "); add_cstrh(&h->dtext[i]); ui_clrtoeol(); } } } } // Draw the "modeline", if we want to. if (want_redraw & WR_MODELINE) { ui_move(ui_lines()-ill-1,0); ui_standout(); n = ui_cols(); for (t=to;t;t=t->link) { if (n < 1) break; if (t != to) { ui_addch(' '); n --; } if (n < 1) break; ui_addnstr(t->on->tag.text,(non->tag.len)?n:t->on->tag.len); n -= t->on->tag.len; if (n < 1) break; ui_addch(':'); n --; if (n < 1) break; l = strlen(t->dest); ui_addnstr(t->dest,(n 0) { ui_addch(' '); n --; } ui_standend(); } // Figure out where the input editor cursor is, at least. ilo = 0; // The lowest Y value occupied by the input editor. y = ui_lines() - ill; cp = ie->curs + pl; cl = cp / ui_cols(); if (cp < ui_cols()*-y) { y = - cl; } if (y < 0) { ilo += ui_cols() * -y; y = 0; } cy = y + ((cp - ilo) / ui_cols()); cx = (cp - ilo) % ui_cols(); if ((ie->mark >= 0) && (ie->curs >= ie->mark)) { cx ++; if (cx >= ui_cols()) { cx = 0; cy ++; } } // Redraw the input editor, if needed. if (want_redraw & WR_INPUT) { while (y < ui_lines()) { ui_move(y,0); n = (y < ui_lines()-1) ? ui_cols() : tl-ilo; if (ilo+n <= pl) { ui_standout(); ui_addnstr(es_buf(&ie->pref)+ilo,n); ui_standend(); } else if (ilo < pl) { ui_standout(); ui_addnstr(es_buf(&ie->pref)+ilo,pl-ilo); ui_standend(); x = n - (pl - ilo); if ((ie->mark >= 0) && (ie->mark < x)) { if (ie->mark > 0) ui_addnstr(es_buf(&ie->line),ie->mark); ui_standout(); ui_addch('·'); ui_standend(); if (ie->mark+1 < x) ui_addnstr(es_buf(&ie->line)+ie->mark,x-(ie->mark+1)); } else { ui_addnstr(es_buf(&ie->line),x); } } else { if ((ie->mark < 0) || (ie->mark > ilo-pl+n)) { ui_addnstr(es_buf(&ie->line)+(ilo-pl),n); } else if (ie->mark < ilo-pl) { ui_addnstr(es_buf(&ie->line)+(ilo-pl)-1,n); } else { if (ie->mark > ilo-pl) ui_addnstr(es_buf(&ie->line)+(ilo-pl),ie->mark-(ilo-pl)); ui_standout(); ui_addch('·'); ui_standend(); if (ie->mark+1 < ilo-pl+n) ui_addnstr(es_buf(&ie->line)+ie->mark,(ilo-pl)+n-(ie->mark+1)); } } if (n < ui_cols()) ui_clrtoeol(); y ++; ilo += ui_cols(); } } return((XY){cx,cy}); } static XY redraw_scrollback(void) { int i; OHLINE *h; int y; int l; int nhidden; h = scrollback.line; if (! h) abort(); // Draw history. if (want_redraw & WR_HIST) { ui_standend(); if (scrollback.lwl < 0) { y = - scrollback.lwl; l = 0; for (i=y-1;i>=0;i--) { ui_move(i,0); ui_addch('~'); ui_clrtoeol(); } } else { y = 0; l = scrollback.lwl; } if (! ohl_is_hidden(h)) { ohl_compute_dl(h); if (l >= h->dl_) { h = h->anlink; l = 0; } } nhidden = 0; while (1) { if (! h) break; if (y >= ui_lines()-1) break; if (ohl_is_hidden(h)) { nhidden ++; if (!h->anlink || !ohl_is_hidden(h->anlink)) { ui_move(y,0); add_hidden_disp(nhidden); ui_clrtoeol(); nhidden = 0; y ++; } h = h->anlink; } else { ohl_compute_dl(h); ui_move(y,0); if (l) ui_addstr(" "); add_cstrh(&h->dtext[l]); ui_clrtoeol(); y ++; l ++; if (l >= h->dl_) { h = h->anlink; l = 0; } } } for (i=ui_lines()-2;i>=y;i--) { ui_move(i,0); ui_addch('~'); ui_clrtoeol(); } } // Draw the "modeline". if ((sblastout != outserial) || (want_redraw & WR_INPUT)) { int cols_out; char *txt_out; const char *bt; int cols_txt; int div; sblastout = outserial; if (sbserial != outserial) { cols_out = asprintf(&txt_out,"%llu",outserial-sbserial); } else { txt_out = 0; cols_out = 0; } if (sb_countlen) { bt = es_buf(&sb_countstr); cols_txt = es_len(&sb_countstr); div = 1; } else { bt = "Viewing scrollback"; cols_txt = 18; // strlen(bt) div = 2; } if (ui_cols() < 5) { ui_move(ui_lines()-1,0); ui_clrtoeol(); ui_addnstr(" ",1); ui_standout(); ui_addnstr(" ",ui_cols()-2); ui_standend(); } else { if (cols_txt+(cols_out?1:0)+cols_out > ui_cols()-2) { do { if (cols_out > 1) { free(txt_out); txt_out = strdup("*"); cols_out = 1; if (cols_txt+1+1 <= ui_cols()-2) break; } l = (cols_txt + (cols_out?1:0) + cols_out - (ui_cols() - 2)) / div; bt += l; cols_txt = ui_cols() - 2 - (cols_out?1:0) - cols_out; } while (0); } ui_move(ui_lines()-1,0); ui_clrtoeol(); if (cols_out) { ui_move(ui_lines()-1,cols_out+1+((ui_cols()-2-cols_out-1-cols_txt)/2)); ui_standout(); ui_addnstr(bt,cols_txt); ui_standend(); ui_move(ui_lines()-1,1); ui_standout(); ui_addnstr(txt_out,cols_out); } else { ui_move(ui_lines()-1,(ui_cols()-cols_txt)/2); ui_standout(); ui_addnstr(bt,cols_txt); } ui_standend(); } } return((XY){ui_cols()-1,ui_lines()-1}); } static void redraw_clock(void) { TSTAMP ts; time_t tt; struct tm *tm; char txt[19]; int l; ts = ts_now(); tt = ts / 1000000ULL; tm = localtime(&tt); if (clock_show_seconds) { l = sprintf(&txt[0]," %02d-%02d-%02d %02d:%02d:%02d", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } else { l = sprintf(&txt[0]," %02d-%02d-%02d %02d:%02d", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); } ui_move(0,ui_cols()-l); ui_standout(); ui_addnstr(&txt[0],l); ui_standend(); } static void record_wrhist(void) { WRHIST *h; WRHIST **hp; hp = &wrhist; while ((h = *hp)) { if (h->value == want_redraw) { *hp = h->link; h->link = wrhist; wrhist = h; h->count ++; return; } else { hp = &h->link; } } h = malloc(sizeof(WRHIST)); h->value = want_redraw; h->count = 1; h->link = wrhist; wrhist = h; return; } static int maybe_redraw(void *arg __attribute__((__unused__))) { XY cloc; record_wrhist(); if (! want_redraw) return(AIO_BLOCK_NIL); if (want_redraw & WR_WINCH) want_redraw = WR_ALL; if (scrollback.line) { cloc = redraw_scrollback(); } else { cloc = redraw_normal(); } if (want_redraw & (WR_HIST|WR_CLOCK)) redraw_clock(); ui_move(cloc.y,cloc.x); want_redraw = 0; ui_refresh(); return(AIO_BLOCK_LOOP); } static int tick_clock(void *arg __attribute__((__unused__))) { TSTAMP now; now = ts_now(); if (now >= nexttick) { want_redraw |= WR_CLOCK; reset_nexttick(now); return(AIO_BLOCK_LOOP); } return(((nexttick-now)/1000ULL)+1); } static void start_clock(void) { clockid = aio_add_block(&tick_clock,0); nexttick = 0; } static void ihist_init(IHIST *h) { h->size = 0; h->newest = 0; h->oldest = 0; } static void edit_init(EDIT *e, const char *pref) { es_init(&e->pref); if (*pref) es_append_n(&e->pref,pref,strlen(pref)); es_init(&e->line); e->curs = 0; e->mark = -1; e->icookie = 0; e->nexticookie = 0; e->hcurs = 0; ihist_init(&e->ihist); } static void init_ui(void) { ui_init(); edit_init(&ie_normal,""); edit_init(&ie_slash,"Command: /"); edit_init(&ie_colon,":"); ie = &ie_normal; ie_nextchar = &nextchar_normal; scrollback.line = 0; es_init(&sb_countstr); sb_countlen = 0; lastilines = -1; want_redraw = WR_ALL; redraw_id = aio_add_block(&maybe_redraw,0); to = 0; listens = 0; debugn = 0; killring.oldest = 0; killring.newest = 0; killring.n = 0; outserial = 0; } static int wtest_server(void *sv) { return(aio_oq_nonempty(&((SERVER *)sv)->oq)); } static void wr_server(void *sv) { SERVER *s; s = sv; wr_common(&s->oq,s->s,"network"); } void record_joined(SERVER *s, const char *cstr, int clen) { ONCHAN *o; for (o=listens;o;o=o->link) { if ((o->s == s) && (o->c.len == clen) && !bcmp(o->c.text,cstr,clen)) return; } o = malloc(sizeof(ONCHAN)+clen); bcopy(cstr,o+1,clen); o->s = s; o->c.text = (void *)(o+1); o->c.len = clen; o->link = listens; listens = o; } void record_parted(SERVER *s, const char *cstr, int clen) { ONCHAN *o; ONCHAN **op; op = &listens; while ((o = *op)) { if ((o->s == s) && (o->c.len == clen) && !bcmp(o->c.text,cstr,clen)) { *op = o->link; free(o); } else { op = &o->link; } } } void print_timeunits(FILE *to, unsigned int sec) { int any; any = 0; if (sec >= 31556952) { fprintf(to,"%dy",sec/31556952); sec %= 31556952; any = 1; } if (any || (sec >= 2629746)) { fprintf(to,"%dm",sec/2629746); sec %= 2629746; any = 1; } if (any || (sec >= 604800)) { fprintf(to,"%dw",sec/604800); sec %= 604800; any = 1; } if (any || (sec >= 86400)) { fprintf(to,"%dd",sec/86400); sec %= 86400; any = 1; } if (any || (sec >= 3600)) { fprintf(to,"%dh",sec/3600); sec %= 3600; any = 1; } if (any || (sec >= 60)) { fprintf(to,"%dm",sec/60); sec %= 60; any = 1; } fprintf(to,"%ds",sec); } void print_unixtime(FILE *to, time_t ts) { struct tm *lt; lt = localtime(&ts); fprintf(to,"%04d-%02d-%02d %02d:%02d:%02d",lt->tm_year+1900,lt->tm_mon+1,lt->tm_mday,lt->tm_hour,lt->tm_min,lt->tm_sec); } static void auto_reconnect(SERVER *s) { hlprintf_srv(s,"%s auto-reconnecting",s->stext); s->reconn_delay = 0; server_noids(s); s->bid = aio_add_block(&start_connection,s); } static void rd_server(void *sv) { SERVER *s; char rbuf[8192]; int nr; s = sv; nr = read(s->s,&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } hlprintf_srv(s,"%s (%s) read: %s",s->stext,s->ctext,strerror(errno)); server_dead(s); auto_reconnect(s); return; } if (nr == 0) { hlprintf_srv(s,"%s (%s) read EOF\n",s->stext,s->ctext); server_dead(s); auto_reconnect(s); return; } (*s->ops->input)(s,&rbuf[0],nr); } static int ping_server(void *sv) { SERVER *s; static unsigned long long int seq = 0; TSTAMP now; TSTAMP next; s = sv; now = ts_now(); if (now > s->lastpong+(PONG_TIMEOUT*TS_SECONDS)) { hlprintf_int("Ping timeout %s",s->stext); server_dead(s); auto_reconnect(s); return(AIO_BLOCK_LOOP); } next = s->lastping + (PING_INTERVAL * TS_SECONDS); if (now < next) { next = ((next - now) / 1000ULL) + 1; if (next > 1000000) next = 1000000; return(next); } (*s->ops->sendping)(s,seq); //hlprintf_int("Sent PING to %s",s->stext); s->lastping = now; return(AIO_BLOCK_LOOP); } static void server_connected(SERVER *s, int fd, char *text) { set_nbio(fd); s->s = fd; s->ctext = strdup(text); hlprintf_int("Connection to %s succeeded at %s",s->stext,text); (*s->ops->connected)(s); if (s->pid != AIO_NOID) abort(); s->pid = aio_add_poll(fd,&aio_rwtest_always,&wtest_server,&rd_server,&wr_server,s); s->lastping = ts_now(); s->lastpong = s->lastping; s->pingid = aio_add_block(&ping_server,s); s->reconn_delay = -1; } static void do_delayed_connect(SERVER *s) { server_noids(s); s->bid = aio_add_block(&start_connection,s); } static void delayed_connect_timeout(void *sv) { SERVER *s; s = sv; aio_remove_poll(s->pid); s->pid = AIO_NOID; close(s->s); do_delayed_connect(s); } static void delayed_reconnect(SERVER *s) { server_noids(s); if (s->reconn_delay < 0) return; if (s->reconn_delay > 0) { struct itimerval itv; s->s = socket(AF_TIMER,SOCK_STREAM,0); if (s->s < 0) { hlprintf_int("AF_TIMER socket: %s\n",strerror(errno)); s->reconn_delay = -1; return; } itv.it_value.tv_sec = s->reconn_delay; itv.it_value.tv_usec = 0; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(s->s,&itv,sizeof(itv)); s->pid = aio_add_poll(s->s,&aio_rwtest_always,&aio_rwtest_never,&delayed_connect_timeout,0,s); s->reconn_delay += s->reconn_delay / 2; if (s->reconn_delay > 3600) s->reconn_delay = 3600; return; } s->reconn_delay = 60; do_delayed_connect(s); } static int scip_next(SCIP *scip) { SERVER *s; s = scip->srv; scip->ai = scip->ai->ai_next; if (! scip->ai) { hlprintf_int("Connection to %s failed",s->stext); s->s = -1; free(s->ctext); s->ctext = 0; if ((s->pid != AIO_NOID) || (s->bid != AIO_NOID)) abort(); scip_done(scip); delayed_reconnect(s); return(1); } server_noids(s); s->bid = aio_add_block(&scip_connect,scip); return(0); } static void scip_check_connect(void *scipv) { SCIP *scip; int e; socklen_t elen; scip = scipv; elen = sizeof(e); aio_remove_poll(scip->srv->pid); if (scip->srv->scip != scip) abort(); scip->srv->pid = AIO_NOID; if (getsockopt(scip->conn,SOL_SOCKET,SO_ERROR,&e,&elen) < 0) { hlprintf_int("Connecting to %s (%s): error getsockopt failed: %s",scip->srv->stext,scip->text,strerror(errno)); } else if (e) { hlprintf_int("Connecting to %s (%s): connect: %s",scip->srv->stext,scip->text,strerror(e)); } else { scip->srv->scip = 0; server_connected(scip->srv,scip->conn,scip->text); scip_done(scip); return; } scip_next(scip); } static int scip_connect(void *scipv) { SCIP *scip; char hstr[NI_MAXHOST]; char sstr[NI_MAXSERV]; int e; scip = scipv; aio_remove_block(scip->srv->bid); scip->srv->bid = AIO_NOID; if (scip->srv->scip != scip) abort(); while (1) { e = getnameinfo(scip->ai->ai_addr,scip->ai->ai_addrlen,&hstr[0],NI_MAXHOST,&sstr[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) { hlprintf_int("getnameinfo() failure for AF %d: %s",scip->ai->ai_family,gai_strerror(e)); } else { free(scip->text); asprintf(&scip->text,"%s/%s",&hstr[0],&sstr[0]); scip->conn = socket(scip->ai->ai_family,scip->ai->ai_socktype,scip->ai->ai_protocol); if (scip->conn < 0) { hlprintf_int("Connecting to %s (%s): socket: %s",scip->srv->stext,scip->text,strerror(errno)); } else { set_nbio(scip->conn); hlprintf_int("Connecting to %s at %s",scip->srv->stext,scip->text); if (connect(scip->conn,scip->ai->ai_addr,scip->ai->ai_addrlen) < 0) { if (errno == EINPROGRESS) { server_noids(scip->srv); scip->srv->pid = aio_add_poll(scip->conn,&aio_rwtest_never,&aio_rwtest_always,0,&scip_check_connect,scip); return(AIO_BLOCK_LOOP); } hlprintf_int("Connecting to %s (%s): connect: %s",scip->srv->stext,scip->text,strerror(errno)); } else { scip->srv->scip = 0; server_connected(scip->srv,scip->conn,scip->text); scip_done(scip); return(AIO_BLOCK_LOOP); } } } if (scip_next(scip)) return(AIO_BLOCK_LOOP); } } static void start_connect(void *sv, int err, struct addrinfo *ai0) { SERVER *s; SCIP *scip; s = sv; if (err) { hlprintf_int("Looking up %s failed: %s",s->stext,gai_strerror(err)); delayed_reconnect(s); return; } scip = malloc(sizeof(SCIP)); scip->srv = s; scip->ai0 = ai0; scip->ai = ai0; scip->text = 0; scip->conn = -1; s->scip = scip; server_noids(s); s->bid = aio_add_block(&scip_connect,scip); } static int start_connection(void *sv) { SERVER *s; const char *why; s = sv; aio_remove_block(s->bid); s->bid = AIO_NOID; why = (*s->ops->canuse)(s); if (why) { hlprintf_int("Can't make %s connection to %s [%s]",s->ops->name,s->stext,why); return(AIO_BLOCK_LOOP); } hlprintf_int("Looking up %s",s->stext); resolver_request(s->host,s->port?:(*s->ops->defaultport)(),&start_connect,s); return(AIO_BLOCK_LOOP); } static void start_connecting(void) { if (config.servers) { SERVER *s; for (s=config.servers;s;s=s->link) { server_noids(s); s->bid = aio_add_block(&start_connection,s); } } else { hlprintf_int("No servers configured"); } } static void handle_sigwinch(int sig __attribute__((__unused__))) { if (winchpend) return; winchpend = 1; write(winchpipe[1],"",1); } static void process_winch(void *arg __attribute__((__unused__))) { char b[64]; winchpend = 0; while (read(winchpipe[0],&b[0],sizeof(b)) > 0) ; ui_done(); ui_init(); want_redraw |= WR_WINCH; } static void setup_sigwinch(void) { struct sigaction sa; winchpend = 0; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&winchpipe[0]) < 0) { fprintf(stderr,"%s: AF_LOCAL/SOCK_STREAM socketpair: %s\n",__progname,strerror(errno)); exit(1); } set_nbio(winchpipe[0]); set_nbio(winchpipe[1]); sa.sa_handler = &handle_sigwinch; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGWINCH,&sa,0); winchid = aio_add_poll(winchpipe[0],&aio_rwtest_always,&aio_rwtest_never,&process_winch,0,0); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); aio_poll_init(); resolver_init(); get_login(); get_fullname(); load_config(); start_input(); init_history(); start_ager(); start_clock(); setup_sigwinch(); init_ui(); start_connecting(); aio_event_loop(); return(0); }