circo.c (37856B)
1 /* See LICENSE file for copyright and license details. 2 * 3 * circo is an IRC client... 4 * 5 * The messages handlers are organized in an array which is accessed whenever a 6 * new message has been fetched. This allows message dispatching in O(1) time. 7 * 8 * Keys are organized as arrays and defined in config.h. 9 * 10 * To understand everything else, start reading main(). 11 */ 12 13 #define _GNU_SOURCE 14 #include <ctype.h> 15 #include <errno.h> 16 #include <locale.h> 17 #include <netdb.h> 18 #include <netinet/in.h> 19 #include <signal.h> 20 #include <stdarg.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 #include <sys/ioctl.h> 25 #include <sys/select.h> 26 #include <sys/socket.h> 27 #include <sys/types.h> 28 #include <sys/wait.h> 29 #include <termios.h> 30 #include <time.h> 31 #include <unistd.h> 32 #include <wchar.h> 33 34 #include "arg.h" 35 char *argv0; 36 37 /* macros */ 38 #define LENGTH(X) (sizeof X / sizeof X[0]) 39 #define ISCHANPFX(P) ((P) == '#' || (P) == '&') 40 #define ISCHAN(B) ISCHANPFX((B)->name[0]) 41 42 /* UTF-8 utils */ 43 #define UTF8BYTES(X) ( ((X) & 0xF0) == 0xF0 ? 4 \ 44 : ((X) & 0xE0) == 0xE0 ? 3 \ 45 : ((X) & 0xC0) == 0xC0 ? 2 \ 46 : 1) 47 #define UTF8CBYTE(X) (((X) & 0xC0) == 0x80) 48 49 /* drawing flags */ 50 #define REDRAW_BAR 1<<1 51 #define REDRAW_BUFFER 1<<2 52 #define REDRAW_CMDLN 1<<3 53 #define REDRAW_ALL (REDRAW_BAR|REDRAW_BUFFER|REDRAW_CMDLN) 54 55 /* VT100 escape sequences */ 56 #define CLEAR "\33[2J" 57 #define CLEARLN "\33[2K" 58 #define CLEARRIGHT "\33[0K" 59 #define CURPOS "\33[%d;%dH" 60 #define CURSON "\33[?25h" 61 #define CURSOFF "\33[?25l" 62 /* colors */ 63 #define COLFG "\33[38;5;%dm" 64 #define COLBG "\33[48;5;%dm" 65 #define ATTR "\33[%dm" 66 #define COLRST "\33[0m" 67 68 /* UI colors and attributes */ 69 #define UI_BYTE 0x19 70 #define UI_SET(X) UI_BYTE, X 71 #define UI_RST UI_BYTE, -1 72 #define UI_WRAP(A,B) UI_SET(B), A, UI_RST 73 #define UI_FMT "%c%d" 74 #define _C_ UI_FMT 75 76 #if defined CTRL && defined _AIX 77 #undef CTRL 78 #endif 79 #ifndef CTRL 80 #define CTRL(k) ((k) & 0x1F) 81 #endif 82 #define ALT(k) ((k) + (161 - 'a')) 83 #define CTRL_ALT(k) ((k) + (129 - 'a')) 84 85 /* enums */ 86 enum { KeyFirst = -999, KeyUp, KeyDown, KeyRight, KeyLeft, KeyHome, KeyEnd, KeyDel, KeyPgUp, KeyPgDw, KeyBackspace, KeyLast }; 87 enum { LineToOffset, OffsetToLine, TotalLines }; /* bufinfo() flags */ 88 89 enum { 90 NickNormal, 91 NickMention, 92 IRCMessage, 93 ColorLast 94 } Color; 95 96 typedef union { 97 int i; 98 unsigned int ui; 99 float f; 100 const void *v; 101 } Arg; 102 103 typedef struct Nick Nick; 104 struct Nick { 105 char name[16]; 106 int len; 107 Nick *next; 108 }; 109 110 typedef struct Buffer Buffer; 111 struct Buffer { 112 char *data; 113 char name[64]; 114 char *hist; 115 char cmdbuf[256]; 116 int size, len, kicked; 117 int line, nlines, lnoff; 118 int cmdlen, cmdoff, cmdpos; 119 int histsz, histlnoff; 120 int need_redraw; 121 int notify; 122 int totnames; 123 int recvnames; 124 Nick *names; 125 Buffer *next; 126 }; 127 128 typedef struct { 129 char *name; 130 void (*func)(char *, char *); 131 } Command; 132 133 typedef struct { 134 char *name; 135 void (*func)(char *, char *, char *); 136 } Message; 137 138 typedef struct { 139 const int key; 140 void (*func)(const Arg *); 141 const Arg arg; 142 } Key; 143 144 /* function declarations */ 145 void attach(Buffer *b); 146 int bprintf(Buffer *b, char *fmt, ...); 147 int bprintf_prefixed(Buffer *b, char *fmt, ...); 148 int bufinfo(char *buf, int len, int val, int act); 149 int bvprintf(Buffer *b, char *fmt, va_list ap); 150 void cleanup(void); 151 void cmd_close(char *cmd, char *s); 152 void cmd_msg(char *cmd, char *s); 153 void cmd_quit(char *cmd, char *s); 154 void cmd_rejoinall(char *cmd, char *s); 155 void cmd_server(char *cmd, char *s); 156 void cmd_topic(char *cmd, char *s); 157 void cmdln_chldel(const Arg *arg); 158 void cmdln_chrdel(const Arg *arg); 159 void cmdln_clear(const Arg *arg); 160 void cmdln_complete(const Arg *arg); 161 void cmdln_cursor(const Arg *arg); 162 void cmdln_submit(const Arg *arg); 163 void cmdln_wdel(const Arg *arg); 164 void detach(Buffer *b); 165 void destroy(Buffer *b); 166 int dial(char *host, char *port, int flags); 167 void die(const char *fmt, ...); 168 void draw(void); 169 void drawbar(void); 170 void drawbuf(void); 171 void drawcmdln(void); 172 void *ecalloc(size_t nmemb, size_t size); 173 Buffer *getbuf(char *name); 174 void focus(Buffer *b); 175 void focusnext(const Arg *arg); 176 void focusnum(const Arg *arg); 177 void focusprev(const Arg *arg); 178 void freebuf(Buffer *b); 179 void freenames(Nick **names); 180 char *gcsfitcols(char *s, int maxw); 181 int gcswidth(char *s, int len); 182 int getkey(void); 183 void hangsup(void); 184 void history(const Arg *arg); 185 void histpush(char *buf, int len); 186 int logfmt(char *fmt, ...); 187 int mvprintf(int x, int y, char *fmt, ...); 188 Buffer *newbuf(char *name); 189 Nick *nickadd(Buffer *b, char *name); 190 void nickdel(Buffer *b, char *name); 191 Nick *nickget(Buffer *b, char *name); 192 void nicklist(Buffer *b, char *list); 193 void nickmv(char *old, char *new); 194 void parsecmd(char *cmd); 195 void parsesrv(void); 196 void privmsg(char *to, char *txt); 197 void quit(char *msg); 198 int readchar(void); 199 void recv_busynick(char *u, char *u2, char *u3); 200 void recv_join(char *who, char *chan, char *txt); 201 void recv_kick(char *who, char *chan, char *txt); 202 void recv_luserme(char *a, char *b, char *c); 203 void recv_mode(char *u, char *val, char *u2); 204 void recv_motd(char *u, char *u2, char *txt); 205 void recv_names(char *usr, char *par, char *txt); 206 void recv_namesend(char *host, char *par, char *names); 207 void recv_nick(char *who, char *u, char *txt); 208 void recv_notice(char *who, char *u, char *txt); 209 void recv_part(char *who, char *chan, char *txt); 210 void recv_ping(char *u, char *u2, char *txt); 211 void recv_privmsg(char *from, char *to, char *txt); 212 void recv_quit(char *who, char *u, char *txt); 213 void recv_topic(char *who, char *chan, char *txt); 214 void recv_topicrpl(char *usr, char *par, char *txt); 215 void resize(int x, int y); 216 void scroll(const Arg *arg); 217 void sendident(void); 218 void setup(void); 219 void sigchld(int unused); 220 void sigwinch(int unused); 221 char *skip(char *s, char c); 222 void sout(char *fmt, ...); 223 void spawn(const char **cmd); 224 void stripformats(char *s); 225 void trim(char *s); 226 int uiset(char *buf, int index); 227 void usage(void); 228 void usrin(void); 229 char *wordleft(char *str, int offset, int *size); 230 231 /* variables */ 232 FILE *srv, *logp; 233 Buffer *buffers, *status, *sel; 234 char bufin[4096]; 235 char bufout[4096]; 236 struct termios origti; 237 time_t trespond; 238 int running = 1; 239 int online = 0; 240 int rows, cols; 241 242 Message messages[] = { 243 { "JOIN", recv_join }, 244 { "KICK", recv_kick }, 245 { "MODE", recv_mode }, 246 { "NICK", recv_nick }, 247 { "NOTICE", recv_notice }, 248 { "PART", recv_part }, 249 { "PING", recv_ping }, 250 { "PRIVMSG", recv_privmsg }, 251 { "QUIT", recv_quit }, 252 { "TOPIC", recv_topic }, 253 { "255", recv_luserme }, 254 { "331", recv_topicrpl }, /* no topic set */ 255 { "332", recv_topicrpl }, 256 { "353", recv_names }, 257 { "366", recv_namesend }, 258 { "372", recv_motd }, 259 { "433", recv_busynick }, 260 { "437", recv_busynick }, 261 262 /* ignored */ 263 { "PONG", NULL }, 264 { "470", NULL }, /* channel forward */ 265 266 }; 267 268 /* configuration, allows nested code to access above variables */ 269 #include "config.h" 270 271 /* function implementations */ 272 void 273 attach(Buffer *b) { 274 b->next = buffers; 275 buffers = b; 276 } 277 278 int 279 bprintf(Buffer *b, char *fmt, ...) { 280 va_list ap; 281 int len; 282 283 va_start(ap, fmt); 284 len = bvprintf(b, fmt, ap); 285 va_end(ap); 286 return len; 287 } 288 289 int 290 bprintf_prefixed(Buffer *b, char *fmt, ...) { 291 va_list ap; 292 time_t t; 293 struct tm *tm; 294 char buf[64]; 295 int len = 0; 296 297 if(*prefix_format) { 298 t = time(NULL); 299 tm = localtime(&t); 300 len = strftime(buf, sizeof buf, prefix_format, tm); 301 if(!len) 302 len = strftime(buf, sizeof buf, "%T | ", tm); /* fallback */ 303 buf[len] = '\0'; 304 len = bprintf(b, "%s", buf); 305 } 306 va_start(ap, fmt); 307 308 #ifdef DEBUG 309 int olen = b->len; 310 len += bvprintf(b, fmt, ap); 311 logfmt("%ld DEBUG bvprintf() %s", time(NULL), &b->data[olen]); /* \n is already there from the caller */ 312 #else 313 len += bvprintf(b, fmt, ap); 314 #endif 315 va_end(ap); 316 return len; 317 } 318 319 int 320 bufinfo(char *buf, int len, int val, int act) { 321 int x, y, i; 322 323 for(i = 0, x = y = 1; i < len; ++i) { 324 switch(act) { 325 case LineToOffset: 326 if(val == y) 327 return i; 328 break; 329 case OffsetToLine: 330 if(val == i) 331 return y; 332 break; 333 } 334 if(x == cols || buf[i] == '\n') { 335 if(buf[i] != '\n' && i < len - 1 && buf[i + 1] == '\n') 336 ++i; 337 x = 1; 338 ++y; 339 } 340 else 341 ++x; 342 } 343 return (act == TotalLines ? y : 0) - 1; 344 } 345 346 int 347 bvprintf(Buffer *b, char *fmt, va_list ap) { 348 va_list ap2; 349 int len; 350 351 va_copy(ap2, ap); 352 len = vsnprintf(&b->data[b->len], b->size - b->len, fmt, ap); 353 if(len >= b->size - b->len) { 354 b->size += len + 1; 355 b->data = realloc(b->data, b->size); 356 if(!b->data) 357 die("realloc():"); 358 len = vsnprintf(&b->data[b->len], b->size - b->len, fmt, ap2); 359 } 360 va_end(ap2); 361 if(len < 0) 362 return -1; 363 b->len += len; 364 b->nlines = bufinfo(b->data, b->len, 0, TotalLines); 365 b->need_redraw |= REDRAW_BUFFER; 366 return len; 367 } 368 369 void 370 cleanup(void) { 371 Buffer *b; 372 373 while((b = buffers)) { 374 buffers = buffers->next; 375 freebuf(b); 376 } 377 tcsetattr(0, TCSANOW, &origti); 378 } 379 380 void 381 cmd_close(char *cmd, char *s) { 382 Buffer *b; 383 384 b = *s ? getbuf(s) : sel; 385 if(!b) { 386 bprintf_prefixed(status, "/%s: %s: unknown buffer.\n", cmd, s); 387 return; 388 } 389 if(b == status) { 390 bprintf_prefixed(status, "/%s: cannot close the status.\n", cmd); 391 return; 392 } 393 if(srv && ISCHAN(b) && !b->kicked) 394 sout("PART :%s", b->name); /* Note: you may be not in that channel */ 395 destroy(b); 396 } 397 398 void 399 cmd_msg(char *cmd, char *s) { 400 char *to, *txt; 401 402 if(!srv) { 403 bprintf_prefixed(sel, "/%s: not connected.\n", cmd); 404 return; 405 } 406 trim(s); 407 to = s; 408 txt = skip(to, ' '); 409 if(!(*to && *txt)) { 410 bprintf_prefixed(sel, "Usage: /%s <channel or user> <text>\n", cmd); 411 return; 412 } 413 privmsg(to, txt); 414 } 415 416 void 417 cmd_quit(char *cmd, char *msg) { 418 if(srv) 419 quit(*msg ? msg : QUIT_MESSAGE); 420 running = 0; 421 } 422 423 void 424 cmd_rejoinall(char *cmd, char *s) { 425 Buffer *b; 426 427 if(!srv) { 428 bprintf_prefixed(sel, "/%s: not connected.\n", cmd); 429 return; 430 } 431 for(b = buffers; b; b = b->next) 432 if(ISCHAN(b)) 433 sout("JOIN %s", b->name); 434 } 435 436 void 437 cmd_server(char *cmd, char *s) { 438 char *t; 439 int fd; 440 441 t = skip(s, ' '); 442 if(!*t) 443 t = port; 444 else 445 strncpy(port, t, sizeof port); 446 t = s; 447 if(!*t) 448 t = host; 449 else 450 strncpy(host, t, sizeof host); 451 if(srv) 452 quit(QUIT_MESSAGE); 453 if((fd = dial(host, port, SOCK_NONBLOCK)) < 0) { 454 bprintf_prefixed(status, "Cannot connect to %s on port %s.\n", host, port); 455 return; 456 } 457 srv = fdopen(fd, "r+"); 458 setbuf(srv, NULL); 459 sel->need_redraw |= REDRAW_BAR; 460 } 461 462 void 463 cmd_topic(char *cmd, char *s) { 464 char *chan, *txt; 465 466 if(!srv) { 467 bprintf_prefixed(sel, "/%s: not connected.\n", cmd); 468 return; 469 } 470 if(!*s) { 471 if(ISCHAN(sel)) 472 sout("TOPIC %s", sel->name); 473 else 474 bprintf_prefixed(sel, "/%s: %s in not a channel.\n", cmd, sel->name); 475 return; 476 } 477 if(ISCHANPFX(*s)) { 478 chan = s; 479 txt = skip(s, ' '); 480 if(!*txt) { 481 sout("TOPIC %s", chan); 482 return; 483 } 484 } 485 else { 486 if(sel == status) { 487 bprintf_prefixed(sel, "Usage: /%s [channel] [text]\n", cmd); 488 return; 489 } 490 chan = sel->name; 491 txt = s; 492 } 493 sout("TOPIC %s :%s", chan, txt); 494 } 495 496 void 497 cmdln_chldel(const Arg *arg) { 498 int nb; 499 500 if(!sel->cmdoff) 501 return; 502 for(nb = 1; UTF8CBYTE(sel->cmdbuf[sel->cmdoff - nb]); ++nb); 503 if(sel->cmdoff < sel->cmdlen) 504 memmove(&sel->cmdbuf[sel->cmdoff - nb], &sel->cmdbuf[sel->cmdoff], 505 sel->cmdlen - sel->cmdoff); 506 sel->cmdlen -= nb; 507 sel->cmdoff -= nb; 508 sel->cmdbuf[sel->cmdlen] = '\0'; 509 sel->need_redraw |= REDRAW_CMDLN; 510 } 511 512 void 513 cmdln_chrdel(const Arg *arg) { 514 int nb; 515 516 if(!sel->cmdlen) 517 return; 518 if(sel->cmdoff == sel->cmdlen) { 519 for(nb = 1; UTF8CBYTE(sel->cmdbuf[sel->cmdoff - nb]); ++nb); 520 sel->cmdoff -= nb; 521 sel->need_redraw |= REDRAW_CMDLN; 522 return; 523 } 524 nb = UTF8BYTES(sel->cmdbuf[sel->cmdoff]); 525 memmove(&sel->cmdbuf[sel->cmdoff], &sel->cmdbuf[sel->cmdoff + nb], 526 sel->cmdlen - sel->cmdoff); 527 528 sel->cmdlen -= nb; 529 sel->cmdbuf[sel->cmdlen] = '\0'; 530 531 if(sel->cmdoff && sel->cmdoff == sel->cmdlen) { 532 for(nb = 1; UTF8CBYTE(sel->cmdbuf[sel->cmdoff - nb]); ++nb); 533 sel->cmdoff -= nb; 534 } 535 536 sel->need_redraw |= REDRAW_CMDLN; 537 } 538 539 void 540 cmdln_clear(const Arg *arg) { 541 if(!sel->cmdoff) 542 return; 543 /* preserve text on the right */ 544 memmove(sel->cmdbuf, &sel->cmdbuf[sel->cmdoff], sel->cmdlen - sel->cmdoff); 545 sel->cmdlen -= sel->cmdoff; 546 sel->cmdbuf[sel->cmdlen] = '\0'; 547 sel->cmdoff = 0; 548 sel->need_redraw |= REDRAW_CMDLN; 549 } 550 551 void 552 cmdln_complete(const Arg *arg) { 553 char word[64]; 554 char *match = NULL, *ws, *we, *epos; 555 int wlen, mlen, newlen, i; 556 Nick *n; 557 558 if(!sel->cmdlen) 559 return; 560 ws = wordleft(sel->cmdbuf, sel->cmdoff, &wlen); 561 if(!ws || wlen > sizeof word) 562 return; 563 memcpy(word, ws, wlen); 564 word[wlen] = '\0'; 565 566 /* actual search */ 567 if(word[0] == '/') { 568 /* search in commands */ 569 for(i = 0; i < LENGTH(commands) && !match; ++i) 570 if(!strncasecmp(commands[i].name, &word[1], wlen - 1)) 571 match = commands[i].name; 572 if(!match) 573 return; 574 mlen = strlen(match); 575 /* preserve the slash */ 576 ++ws; 577 --wlen; 578 } 579 else if(ISCHANPFX(word[0])) { 580 /* search buffer name */ 581 if(strncasecmp(sel->name, word, wlen)) 582 return; 583 match = sel->name; 584 mlen = strlen(match); 585 } 586 else { 587 /* match a nick in current buffer */ 588 for(n = sel->names; n && !match; n = n->next) 589 if(!strncasecmp(n->name, word, wlen)) 590 break; 591 if(!n) 592 return; 593 match = n->name; 594 mlen = n->len; 595 } 596 597 we = ws + wlen; 598 epos = &sel->cmdbuf[sel->cmdoff] > we ? &sel->cmdbuf[sel->cmdoff] : we; 599 600 /* check if match exceed buffer size */ 601 newlen = sel->cmdlen - (epos - ws) + mlen; 602 if(newlen > sizeof sel->cmdbuf - 1) 603 return; 604 605 memmove(ws+mlen, epos, sel->cmdlen - (epos - sel->cmdbuf)); 606 memcpy(ws, match, mlen); 607 608 sel->cmdlen = newlen; 609 sel->cmdbuf[sel->cmdlen] = '\0'; 610 sel->cmdoff = ws - sel->cmdbuf + mlen; 611 sel->need_redraw |= REDRAW_CMDLN; 612 } 613 614 void 615 cmdln_cursor(const Arg *arg) { 616 int i = arg->i, nb; 617 618 if(!i) { 619 sel->cmdoff = 0; 620 sel->need_redraw |= REDRAW_CMDLN; 621 return; 622 } 623 if(i < 0) { 624 nb = 1; 625 while(++i <= 0) 626 while(UTF8CBYTE(sel->cmdbuf[sel->cmdoff - nb])) 627 ++nb; 628 nb = -nb; 629 } 630 else { 631 nb = 0; 632 while(--i >= 0) 633 nb += UTF8BYTES(sel->cmdbuf[sel->cmdoff + nb]); 634 } 635 636 sel->cmdoff += nb; 637 if(sel->cmdoff < 0) 638 sel->cmdoff = 0; 639 else if(sel->cmdoff > sel->cmdlen) 640 sel->cmdoff = sel->cmdlen; 641 sel->need_redraw |= REDRAW_CMDLN; 642 } 643 644 void 645 cmdln_submit(const Arg *arg) { 646 char *buf; 647 648 if(sel->cmdbuf[0] == '\0') 649 return; 650 651 #ifdef DEBUG 652 logfmt("%ld DEBUG cmdln_submit() %s\n", time(NULL), sel->cmdbuf); 653 #endif 654 buf = ecalloc(1, sel->cmdlen); 655 memcpy(buf, sel->cmdbuf, sel->cmdlen); 656 657 histpush(sel->cmdbuf, sel->cmdlen); 658 if(buf[0] == '/') { 659 sel->cmdlen = sel->cmdoff = sel->histlnoff = 0; 660 sel->cmdbuf[sel->cmdlen] = '\0'; 661 sel->need_redraw |= REDRAW_CMDLN; 662 663 /* Note: network latency may cause visual glitches. */ 664 parsecmd(buf); 665 } 666 else { 667 if(sel == status) 668 bprintf_prefixed(sel, "Cannot send text here.\n"); 669 else if(!srv) 670 bprintf_prefixed(sel, "Not connected.\n"); 671 else 672 privmsg(sel->name, sel->cmdbuf); 673 sel->cmdlen = sel->cmdoff = sel->histlnoff = 0; 674 sel->cmdbuf[sel->cmdlen] = '\0'; 675 sel->need_redraw |= REDRAW_CMDLN; 676 } 677 free(buf); 678 } 679 680 void 681 cmdln_wdel(const Arg *arg) { 682 int i; 683 684 if(!sel->cmdoff) 685 return; 686 i = sel->cmdoff - 1; 687 while(i && sel->cmdbuf[i] == ' ') 688 --i; 689 if(i && isalnum(sel->cmdbuf[i])) { 690 while(--i && isalnum(sel->cmdbuf[i])); 691 if(i) 692 ++i; 693 } 694 memmove(&sel->cmdbuf[i], &sel->cmdbuf[sel->cmdoff], sel->cmdlen - sel->cmdoff); 695 sel->cmdlen -= sel->cmdoff - i; 696 sel->cmdbuf[sel->cmdlen] = '\0'; 697 sel->cmdoff = i; 698 sel->need_redraw |= REDRAW_CMDLN; 699 } 700 701 void 702 destroy(Buffer *b) { 703 if(b == sel) { 704 sel = sel->next ? sel->next : buffers; 705 sel->need_redraw |= REDRAW_ALL; 706 } 707 detach(b); 708 freebuf(b); 709 710 } 711 712 void 713 detach(Buffer *b) { 714 Buffer **tb; 715 716 for (tb = &buffers; *tb && *tb != b; tb = &(*tb)->next); 717 *tb = b->next; 718 } 719 720 int 721 dial(char *host, char *port, int flags) { 722 static struct addrinfo hints; 723 struct addrinfo *res, *r; 724 int srvfd; 725 726 memset(&hints, 0, sizeof hints); 727 hints.ai_family = AF_UNSPEC; 728 hints.ai_socktype = SOCK_STREAM; 729 if(getaddrinfo(host, port, &hints, &res) != 0) 730 return -1; 731 for(r = res; r; r = r->ai_next) { 732 if((srvfd = socket(r->ai_family, r->ai_socktype | flags, r->ai_protocol)) == -1) 733 continue; 734 if(connect(srvfd, r->ai_addr, r->ai_addrlen) == 0 || errno == EINPROGRESS) 735 break; 736 close(srvfd); 737 } 738 freeaddrinfo(res); 739 if(!r) 740 return -1; 741 return srvfd; 742 } 743 744 void 745 die(const char *fmt, ...) { 746 va_list ap; 747 va_start(ap, fmt); 748 vfprintf(stderr, fmt, ap); 749 va_end(ap); 750 751 if (fmt[0] && fmt[strlen(fmt)-1] == ':') { 752 fputc(' ', stderr); 753 perror(NULL); 754 } else { 755 fputc('\n', stderr); 756 } 757 758 exit(0); 759 } 760 761 void 762 draw(void) { 763 printf(CURSOFF); 764 if(sel->need_redraw & REDRAW_BAR) 765 drawbar(); 766 if(sel->need_redraw & REDRAW_BUFFER) 767 drawbuf(); 768 if(sel->need_redraw & REDRAW_CMDLN) 769 drawcmdln(); 770 printf(CURPOS CURSON, rows, sel->cmdpos); 771 } 772 773 void 774 drawbar(void) { 775 Buffer *b; 776 char buf[512]; 777 int x = 1, len = 0; 778 779 if(!(cols && rows)) 780 return; 781 782 if(ISCHAN(sel)) 783 len += snprintf(buf, sizeof buf, "%d users in %s", sel->totnames, sel->name); 784 else 785 len += snprintf(buf, sizeof buf, "%s@%s:%s (%s)", 786 *nick ? nick : "[nick unset]", host, port, 787 srv ? (online ? "online" : "connecting...") : "offline"); 788 if(sel->line) 789 len += snprintf(&buf[len], sizeof buf - len, " [scrolled]"); 790 791 #ifdef DEBUG 792 len += snprintf(&buf[len], sizeof buf - len, " | DEBUG"); 793 #endif 794 795 len = gcsfitcols(buf, cols - x + 1) - buf; 796 mvprintf(x, 1, "%.*s", len, buf); 797 x += gcswidth(buf, len); 798 799 for(b = buffers; b; b = b->next) { 800 if(!b->notify) 801 continue; 802 snprintf(buf, sizeof buf, " %s(%d)", b->name, b->notify); 803 len = gcsfitcols(buf, cols - x + 1) - buf; 804 uiset(NULL, NickMention); 805 mvprintf(x, 1, "%.*s", len, buf); 806 uiset(NULL, -1); 807 x += gcswidth(buf, len); 808 } 809 if(x < cols) 810 mvprintf(x, 1, CLEARRIGHT); 811 } 812 813 void 814 drawbuf(void) { 815 int x, y, c, i, nb, nx; 816 char *endp; 817 818 if(!(cols && rows)) 819 return; 820 821 x = rows - 2; 822 y = sel->nlines - x; 823 i = sel->line 824 ? sel->lnoff 825 : sel->len ? bufinfo(sel->data, sel->len, 1 + (sel->nlines > x ? y : 0), LineToOffset) : 0; 826 x = 1; 827 y = 2; 828 829 for(; i < sel->len; ++i) { 830 if(sel->data[i] == UI_BYTE) { 831 c = strtol(&sel->data[++i], &endp, 10); 832 i += endp - &sel->data[i] - 1; 833 uiset(NULL, c); 834 continue; 835 } 836 837 c = sel->data[i]; 838 if(x <= cols) { 839 if(c != '\n') { 840 nb = UTF8BYTES(c); 841 nx = x + gcswidth(&sel->data[i], 1); 842 if(nx - 1 <= cols) { 843 mvprintf(x, y, "%.*s", nb, &sel->data[i]); 844 x = nx; 845 i += nb - 1; 846 continue; 847 } 848 } 849 mvprintf(x, y, "%s", CLEARRIGHT); 850 } 851 852 x = 1; 853 if(++y == rows) 854 break; 855 if(c != '\n') { 856 nb = UTF8BYTES(c); 857 mvprintf(x, y, "%.*s", nb, &sel->data[i]); 858 x += gcswidth(&sel->data[i], 1); 859 i += nb - 1; 860 } 861 if(x > cols && i < sel->len - 1 && sel->data[i + 1] == '\n') 862 ++i; 863 } 864 if(y < rows) { 865 mvprintf(x, y, "%s", CLEARRIGHT); 866 while(++y < rows) 867 mvprintf(1, y, "%s", CLEARLN); 868 } 869 } 870 871 void 872 drawcmdln(void) { 873 char prompt[256], *buf, *p; 874 int s, w; /* size and width */ 875 int x = 1, colw = cols; 876 877 sel->cmdpos = 1; 878 879 /* prompt */ 880 s = snprintf(prompt, sizeof prompt, "[%s] ", sel->name); 881 w = gcswidth(prompt, colw - 1); 882 if(w > 0) { 883 s = gcsfitcols(prompt, colw - 1) - prompt; 884 mvprintf(x, rows, "%.*s", s, prompt); 885 x += w; 886 colw -= w; 887 sel->cmdpos += w; 888 } 889 890 /* buffer */ 891 for(p = buf = sel->cmdbuf; p < &sel->cmdbuf[sel->cmdoff]; p = gcsfitcols(p, colw)) { 892 if(p != sel->cmdbuf && p == buf) 893 break; 894 buf = p; 895 } 896 w = gcswidth(buf, colw); 897 898 /* leave room for the cursor */ 899 if(w >= colw && p == &sel->cmdbuf[sel->cmdoff]) { 900 buf = p; 901 w = gcswidth(buf, colw); 902 } 903 904 s = w ? gcsfitcols(buf, colw) - buf : 0; 905 mvprintf(x, rows, "%.*s%s", s, buf, w < colw ? CLEARRIGHT : ""); 906 907 /* cursor position */ 908 for(p = buf; p < &sel->cmdbuf[sel->cmdoff]; p += UTF8BYTES(*p)) 909 sel->cmdpos += gcswidth(p, 1); 910 } 911 912 void * 913 ecalloc(size_t nmemb, size_t size) { 914 void *p; 915 916 if(!(p = calloc(nmemb, size))) 917 die("Cannot allocate memory."); 918 return p; 919 } 920 921 void 922 focus(Buffer *b) { 923 if(!b) 924 return; 925 if(b->notify) 926 b->notify = 0; 927 sel = b; 928 sel->need_redraw |= REDRAW_ALL; 929 } 930 931 void 932 focusnext(const Arg *arg) { 933 Buffer *nb; 934 935 nb = sel->next ? sel->next : buffers; 936 if(nb == sel) 937 return; 938 focus(nb); 939 } 940 941 void 942 focusnum(const Arg *arg) { 943 Buffer *b; 944 int i = arg->i; 945 946 for(b = buffers; b; b = b->next) 947 if(--i < 0) 948 break; 949 focus(b); 950 } 951 952 void 953 focusprev(const Arg *arg) { 954 Buffer *b, *nb; 955 956 nb = sel; 957 if(sel == buffers) { 958 for(b = buffers; b; b = b->next) 959 nb = b; 960 } 961 else { 962 for(b = buffers; b && b->next; b = b->next) 963 if(b->next == sel) 964 nb = b; 965 } 966 if(nb == sel) 967 return; 968 focus(nb); 969 } 970 971 void 972 freebuf(Buffer *b) { 973 freenames(&b->names); 974 free(b->hist); 975 free(b->data); 976 free(b); 977 } 978 979 void 980 freenames(Nick **names) { 981 Nick *n; 982 983 while((n = *names)) { 984 *names = (*names)->next; 985 free(n); 986 } 987 } 988 989 char * 990 gcsfitcols(char *s, int maxw) { 991 int w = 0; 992 993 while(*s) { 994 w += gcswidth(s, 1); 995 if(w > maxw) 996 break; 997 s += UTF8BYTES(*s); 998 } 999 return s; 1000 } 1001 1002 int 1003 gcswidth(char *s, int len) { 1004 wchar_t *wcs = malloc(len * sizeof(wchar_t) + 1); 1005 int n; 1006 1007 if(!wcs) 1008 return -1; 1009 n = mbstowcs(wcs, s, len); 1010 return n == -1 ? -1 : wcswidth(wcs, n); 1011 } 1012 1013 Buffer * 1014 getbuf(char *name) { 1015 Buffer *b; 1016 1017 for(b = buffers; b; b = b->next) 1018 if(!strcmp(b->name, name)) 1019 return b; 1020 return NULL; 1021 } 1022 1023 /* XXX quick'n dirty implementation */ 1024 int 1025 getkey(void) { 1026 int key = readchar(), c; 1027 1028 if(key != '\x1b' || readchar() != '[') { 1029 switch(key) { 1030 case 127: key = KeyBackspace; break; 1031 } 1032 return key; 1033 } 1034 switch((c = readchar())) { 1035 case 'A': key = KeyUp; break; 1036 case 'B': key = KeyDown; break; 1037 case 'C': key = KeyRight; break; 1038 case 'D': key = KeyLeft; break; 1039 case 'H': key = KeyHome; break; 1040 case 'F': key = KeyEnd; break; 1041 case '1': key = KeyHome; break; 1042 case '3': key = KeyDel; break; 1043 case '4': key = KeyEnd; break; 1044 case '5': key = KeyPgUp; break; 1045 case '6': key = KeyPgDw; break; 1046 case '7': key = KeyHome; break; 1047 case '8': key = KeyEnd; break; 1048 } 1049 readchar(); 1050 return key; 1051 } 1052 1053 void 1054 hangsup(void) { 1055 if(!srv) 1056 return; 1057 fclose(srv); 1058 srv = NULL; 1059 online = 0; 1060 sel->need_redraw |= REDRAW_BAR; 1061 } 1062 1063 void 1064 history(const Arg *arg) { 1065 int nl, n, i; 1066 1067 if(!sel->histsz) 1068 return; 1069 for(i = nl = 0; i < sel->histsz; i += strlen(&sel->hist[i]) + 2, ++nl); 1070 if(!sel->histlnoff) { 1071 if(sel->cmdlen) 1072 histpush(sel->cmdbuf, sel->cmdlen); 1073 sel->histlnoff = nl+1; 1074 } 1075 n = sel->histlnoff + arg->i; 1076 if(n < 1) 1077 n = 1; 1078 else if(n > nl) 1079 n = 0; 1080 sel->histlnoff = n; 1081 if(sel->histlnoff) { 1082 for(i = 0; i < sel->histsz && --n; i += strlen(&sel->hist[i]) + 2); 1083 sel->cmdlen = strlen(&sel->hist[i]); 1084 memcpy(sel->cmdbuf, &sel->hist[i], sel->cmdlen); 1085 } 1086 else { 1087 sel->cmdlen = 0; 1088 } 1089 sel->cmdoff = 0; 1090 sel->cmdbuf[sel->cmdlen] = '\0'; 1091 sel->need_redraw |= REDRAW_CMDLN; 1092 } 1093 1094 void 1095 histpush(char *buf, int len) { 1096 int nl, i; 1097 1098 /* do not clone unchanged lines */ 1099 if(sel->histlnoff) { 1100 for(i = 0, nl = 1; i < sel->histsz && nl != sel->histlnoff; i += strlen(&sel->hist[i]) + 2, ++nl); 1101 if(!memcmp(&sel->hist[i], buf, len)) 1102 return; 1103 } 1104 if(sel->histsz) 1105 ++sel->histsz; 1106 i = sel->histsz; 1107 if(!(sel->hist = realloc(sel->hist, (sel->histsz += len + 1)))) 1108 die("Cannot realloc"); 1109 memcpy(&sel->hist[i], buf, len); 1110 sel->hist[i + len] = '\0'; 1111 } 1112 1113 int 1114 logfmt(char *fmt, ...) { 1115 va_list ap; 1116 int len; 1117 1118 if(!logp) 1119 return -1; 1120 va_start(ap, fmt); 1121 len = vfprintf(logp, fmt, ap); 1122 va_end(ap); 1123 fflush(logp); 1124 return len; 1125 } 1126 1127 int 1128 mvprintf(int x, int y, char *fmt, ...) { 1129 va_list ap; 1130 int len; 1131 1132 printf(CURPOS, y, x); 1133 va_start(ap, fmt); 1134 len = vfprintf(stdout, fmt, ap); 1135 va_end(ap); 1136 return len; 1137 } 1138 1139 Buffer * 1140 newbuf(char *name) { 1141 Buffer *b; 1142 1143 b = ecalloc(1, sizeof(Buffer)); 1144 b->need_redraw = REDRAW_ALL; 1145 strncpy(b->name, name, sizeof b->name); 1146 attach(b); 1147 return b; 1148 } 1149 1150 Nick * 1151 nickadd(Buffer *b, char *name) { 1152 Nick *n; 1153 1154 n = ecalloc(1, sizeof(Nick)); 1155 strncpy(n->name, name, sizeof n->name - 1); 1156 n->len = strlen(n->name); 1157 1158 /* attach */ 1159 n->next = b->names; 1160 b->names = n; 1161 ++b->totnames; 1162 if(b == sel) 1163 sel->need_redraw |= REDRAW_BAR; 1164 return n; 1165 } 1166 1167 void 1168 nickdel(Buffer *b, char *name) { 1169 Nick *n, **tn; 1170 1171 if(!b) 1172 return; 1173 if(!(n = nickget(b, name))) 1174 return; 1175 /* detach */ 1176 for(tn = &b->names; *tn && *tn != n; tn = &(*tn)->next); 1177 *tn = n->next; 1178 free(n); 1179 --b->totnames; 1180 if(b == sel) 1181 sel->need_redraw |= REDRAW_BAR; 1182 } 1183 1184 Nick * 1185 nickget(Buffer *b, char *name) { 1186 Nick *n; 1187 1188 for(n = b->names; n; n = n->next) 1189 if(!strcmp(n->name, name)) 1190 return n; 1191 return NULL; 1192 } 1193 1194 void 1195 nicklist(Buffer *b, char *list) { 1196 char *p, *np; 1197 1198 for(p = list, np = skip(list, ' '); *p; p = np, np = skip(np, ' ')) { 1199 /* skip nick flags */ 1200 switch(*p) { 1201 case '+': 1202 case '@': 1203 case '~': 1204 case '%': 1205 case '&': 1206 ++p; 1207 break; 1208 } 1209 nickadd(b, p); 1210 } 1211 } 1212 1213 void 1214 nickmv(char *old, char *new) { 1215 Buffer *b; 1216 Nick *n; 1217 1218 for(b = buffers; b; b = b->next) { 1219 if(!(ISCHAN(b) && (n = nickget(b, old)))) 1220 continue; 1221 strncpy(n->name, new, sizeof n->name - 1); 1222 n->len = strlen(n->name); 1223 } 1224 } 1225 1226 void 1227 parsecmd(char *cmd) { 1228 char *p, *tp; 1229 int i, len; 1230 1231 p = &cmd[1]; /* skip the slash */ 1232 if(!*p) 1233 return; 1234 tp = p + 1; 1235 tp = skip(p, ' '); 1236 len = strlen(p); 1237 for(i = 0; i < LENGTH(commands); ++i) { 1238 if(!strncmp(commands[i].name, p, len)) { 1239 commands[i].func(p, tp); 1240 return; 1241 } 1242 } 1243 if(srv) 1244 sout("%s %s", p, tp); /* raw */ 1245 else 1246 bprintf_prefixed(sel, "/%s: not connected.\n", p); 1247 } 1248 1249 void 1250 parsesrv(void) { 1251 char *cmd, *usr, *par, *txt; 1252 1253 #ifdef DEBUG 1254 logfmt("%ld DEBUG parsesrv(): %s", time(NULL), bufin); /* \n is already there from fgets() */ 1255 #endif 1256 cmd = bufin; 1257 usr = host; 1258 if(!cmd || !*cmd) 1259 return; 1260 if(cmd[0] == ':') { 1261 usr = cmd + 1; 1262 cmd = skip(usr, ' '); 1263 if(cmd[0] == '\0') 1264 return; 1265 skip(usr, '!'); 1266 } 1267 skip(cmd, '\r'); 1268 par = skip(cmd, ' '); 1269 txt = skip(par, ':'); 1270 1271 trim(txt); 1272 trim(par); 1273 1274 /* IRC formatting may be supported at some point in the future. For now 1275 * just strip that out to keep things simple. */ 1276 stripformats(txt); 1277 1278 for(int i = 0; i < LENGTH(messages); ++i) { 1279 if(!strcmp(messages[i].name, cmd)) { 1280 if(messages[i].func) 1281 messages[i].func(usr, par, txt); 1282 return; 1283 } 1284 } 1285 par = skip(par, ' '); 1286 bprintf_prefixed(sel, "%s %s\n", par, txt); 1287 } 1288 1289 void 1290 privmsg(char *to, char *txt) { 1291 Buffer *b = getbuf(to); 1292 1293 if(!b) 1294 b = isalpha(*to) ? newbuf(to) : sel; 1295 bprintf_prefixed(b, "%s: %s\n", nick, txt); 1296 sout("PRIVMSG %s :%s", to, txt); 1297 logfmt("%ld PRIVMSG to %s: %s\n", time(NULL), to, txt); 1298 } 1299 1300 void 1301 quit(char *msg) { 1302 if(!srv) 1303 return; 1304 sout("QUIT :%s", msg); 1305 hangsup(); 1306 } 1307 1308 int 1309 readchar(void) { 1310 char buf[1] = {0}; 1311 return (read(0, buf, 1) < 1 ? EOF : buf[0]); 1312 } 1313 1314 void 1315 recv_busynick(char *u, char *u2, char *u3) { 1316 char *n = skip(u2, ' '); 1317 1318 bprintf_prefixed(status, "%s is busy, choose a different /nick\n", n); 1319 sel->need_redraw |= REDRAW_BAR; 1320 } 1321 1322 void 1323 recv_join(char *who, char *chan, char *txt) { 1324 Buffer *b; 1325 1326 if(!*chan) 1327 chan = txt; 1328 b = getbuf(chan); 1329 1330 /* don't call nickadd() for ourselves since nicks list gets updated when join */ 1331 if(!strcmp(who, nick)) { 1332 if(!b) 1333 b = newbuf(chan); 1334 else 1335 b->kicked = 0; /* b may only be non-NULL due to this */ 1336 sel = b; 1337 sel->need_redraw = REDRAW_ALL; 1338 } 1339 else 1340 nickadd(b, who); 1341 bprintf_prefixed(b, _C_"%s"_C_" %s\n", UI_WRAP("JOIN", IRCMessage), who); 1342 logfmt("%ld JOIN %s on %s\n", time(NULL), who, chan); 1343 } 1344 1345 void 1346 recv_kick(char *oper, char *chan, char *who) { 1347 Buffer *b; 1348 1349 who = skip(chan, ' '); 1350 b = getbuf(chan); 1351 if(!b) 1352 return; 1353 if(!strcmp(who, nick)) { 1354 b->kicked = 1; 1355 freenames(&b->names); /* we don't need this anymore */ 1356 bprintf_prefixed(b, "You got kicked from %s\n", chan); 1357 } 1358 else { 1359 bprintf_prefixed(b, _C_"%s"_C_" %s (%s)\n", UI_WRAP("KICK", IRCMessage), who, oper); 1360 nickdel(b, who); 1361 } 1362 logfmt("%ld KICK %s on %s (%s)\n", time(NULL), who, chan, oper); 1363 } 1364 1365 void 1366 recv_luserme(char *host, char *mynick, char *info) { 1367 strcpy(nick, mynick); 1368 sel->need_redraw |= REDRAW_BAR; 1369 } 1370 1371 void 1372 recv_mode(char *u, char *val, char *u2) { 1373 if(*nick) 1374 return; 1375 strcpy(nick, val); 1376 sel->need_redraw |= REDRAW_BAR; 1377 } 1378 1379 void 1380 recv_motd(char *u, char *u2, char *txt) { 1381 bprintf_prefixed(status, "%s\n", txt); 1382 } 1383 1384 void 1385 recv_names(char *host, char *par, char *names) { 1386 char *chan = skip(skip(par, ' '), ' '); /* skip user and symbol */ 1387 Buffer *b = getbuf(chan); 1388 1389 if(!b) 1390 b = status; 1391 if(!b->recvnames) { 1392 freenames(&b->names); 1393 b->totnames = 0; 1394 b->recvnames = 1; 1395 } 1396 nicklist(b, names); /* keep as last since names is altered by skip() */ 1397 } 1398 1399 void 1400 recv_namesend(char *host, char *par, char *names) { 1401 char *chan = skip(par, ' '); 1402 Buffer *b = getbuf(chan); 1403 Nick *n; 1404 1405 if(!b) 1406 b = status; 1407 b->recvnames = 0; 1408 if(!b->totnames) { 1409 bprintf_prefixed(sel, "No names in %s.\n", chan); 1410 return; 1411 } 1412 1413 bprintf_prefixed(sel, _C_"%s"_C_" in %s (%d):", UI_WRAP("NAMES", IRCMessage), chan, b->totnames); 1414 logfmt("%ld NAMES %s (%d):", time(NULL), chan, b->totnames); 1415 for(n = b->names; n; n = n->next) { 1416 bprintf(sel, " %s", n->name); 1417 logfmt(" %s", n->name); 1418 } 1419 bprintf(sel, "\n"); 1420 logfmt("\n"); 1421 } 1422 1423 void 1424 recv_nick(char *who, char *u, char *upd) { 1425 Buffer *b; 1426 1427 if(!strcmp(who, nick)) { 1428 strcpy(nick, upd); 1429 sel->need_redraw |= REDRAW_BAR; 1430 bprintf_prefixed(sel, "You're now known as %s\n", upd); 1431 } 1432 for(b = buffers; b; b = b->next) { 1433 if(!(ISCHAN(b) && nickget(b, who))) 1434 continue; 1435 bprintf_prefixed(b, _C_"%s"_C_" %s is now %s\n", UI_WRAP("NICK", IRCMessage), who, upd); 1436 } 1437 nickmv(who, upd); 1438 logfmt("%ld NICK %s is now %s\n", time(NULL), who, upd); 1439 } 1440 1441 void 1442 recv_notice(char *who, char *u, char *txt) { 1443 bprintf_prefixed(sel, _C_"%s"_C_" %s: %s\n", UI_WRAP("NOTICE", IRCMessage), who, txt); 1444 logfmt("%ld NOTICE %s: %s\n", time(NULL), who, txt); 1445 } 1446 1447 void 1448 recv_part(char *who, char *chan, char *txt) { 1449 Buffer *b = getbuf(chan); 1450 1451 /* cmd_close() destroy the buffer before PART is received */ 1452 if(!b) 1453 return; 1454 if(!strcmp(who, nick)) { 1455 destroy(b); 1456 } 1457 else { 1458 bprintf_prefixed(b, _C_"%s"_C_" %s (%s)\n", UI_WRAP("PART", IRCMessage), who, txt); 1459 nickdel(b, who); 1460 } 1461 logfmt("%ld PART %s from %s (%s)\n", time(NULL), who, chan, txt); 1462 } 1463 1464 void 1465 recv_ping(char *u, char *u2, char *txt) { 1466 sout("PONG %s", txt); 1467 } 1468 1469 void 1470 recv_privmsg(char *from, char *to, char *txt) { 1471 Buffer *b; 1472 int mention, query; 1473 1474 query = !strcmp(nick, to); 1475 mention = strstr(txt, nick) != NULL; 1476 1477 if(query) 1478 to = from; 1479 b = getbuf(to); 1480 if(!b) 1481 b = newbuf(to); 1482 1483 if(b != sel && (mention || query)) { 1484 ++b->notify; 1485 sel->need_redraw |= REDRAW_BAR; 1486 if(NOTIFY_SCRIPT) 1487 spawn((const char *[]){ NOTIFY_SCRIPT, from, to, txt, NULL }); 1488 } 1489 bprintf_prefixed(b, _C_"%s"_C_": %s\n", UI_WRAP(from, mention ? NickMention : NickNormal), txt); 1490 if(query) 1491 logfmt("%ld PRIVMSG from %s on %s: %s\n", time(NULL), from, to, txt); 1492 else 1493 logfmt("%ld PRIVMSG from %s: %s\n", time(NULL), from, txt); 1494 } 1495 1496 void 1497 recv_quit(char *who, char *u, char *txt) { 1498 Buffer *b; 1499 1500 for(b = buffers; b; b = b->next) { 1501 if(!(ISCHAN(b) && nickget(b, who))) 1502 continue; 1503 bprintf_prefixed(b, _C_"%s"_C_" %s (%s)\n", UI_WRAP("QUIT", IRCMessage), who, txt); 1504 nickdel(b, who); 1505 } 1506 logfmt("%ld QUIT %s (%s)\n", time(NULL), who, txt); 1507 } 1508 1509 void 1510 recv_topic(char *who, char *chan, char *txt) { 1511 bprintf_prefixed(getbuf(chan), "%s changed topic to %s\n", who, txt); 1512 logfmt("%ld TOPIC %s (%s): %s\n", time(NULL), chan, who, txt); 1513 } 1514 1515 void 1516 recv_topicrpl(char *usr, char *par, char *txt) { 1517 char *chan = skip(par, ' '); 1518 1519 bprintf_prefixed(sel, "Topic on %s is %s\n", chan, txt); /* TODO: who set the topic? */ 1520 } 1521 1522 void 1523 resize(int x, int y) { 1524 Buffer *b; 1525 1526 rows = x; 1527 cols = y; 1528 for(b = buffers; b; b = b->next) { 1529 if(b->line && b->lnoff) 1530 b->lnoff = bufinfo(b->data, b->len, b->line, LineToOffset); 1531 b->nlines = bufinfo(b->data, b->len, 0, TotalLines); 1532 } 1533 } 1534 1535 void 1536 run(void) { 1537 Buffer *b; 1538 struct timeval tv; 1539 fd_set rd; 1540 int n, nfds; 1541 1542 while(running) { 1543 FD_ZERO(&rd); 1544 FD_SET(0, &rd); 1545 tv.tv_sec = 120; 1546 tv.tv_usec = 0; 1547 nfds = 0; 1548 if(srv) { 1549 FD_SET(fileno(srv), &rd); 1550 nfds = fileno(srv); 1551 } 1552 n = select(nfds + 1, &rd, 0, 0, &tv); 1553 if(n < 0) { 1554 if(errno == EINTR) 1555 continue; 1556 die("select()"); 1557 } 1558 if(n == 0) { 1559 if(srv) { 1560 if(time(NULL) - trespond >= 300) { 1561 hangsup(); 1562 for(b = buffers; b; b = b->next) 1563 bprintf_prefixed(b, "Connection timeout.\n"); 1564 } 1565 else 1566 sout("PING %s", host); 1567 } 1568 } 1569 else { 1570 if(srv && FD_ISSET(fileno(srv), &rd)) { 1571 /* TODO: we should keep reading until CRLF is found. Only at that 1572 * point parsesrv(), sendident(), etc. should be called. */ 1573 if(fgets(bufin, sizeof bufin, srv) == NULL) { 1574 for(b = buffers; b; b = b->next) 1575 bprintf_prefixed(b, "%s.\n", online 1576 ? "Remote host closed connection" 1577 : "Cannot connect to the host"); 1578 hangsup(); 1579 } 1580 else { 1581 trespond = time(NULL); 1582 parsesrv(); 1583 if(!online) { 1584 online = 1; 1585 sendident(); 1586 } 1587 } 1588 } 1589 if(FD_ISSET(0, &rd)) 1590 usrin(); 1591 } 1592 if(sel->need_redraw) { 1593 draw(); 1594 sel->need_redraw = 0; 1595 } 1596 } 1597 } 1598 1599 void 1600 scroll(const Arg *arg) { 1601 int bufh = rows - 2; 1602 1603 if(sel->nlines <= bufh) 1604 return; 1605 if(arg->i == 0) { 1606 sel->line = 0; 1607 sel->lnoff = 0; 1608 sel->need_redraw |= (REDRAW_BUFFER | REDRAW_BAR); 1609 return; 1610 } 1611 if(!sel->line) 1612 sel->line = sel->nlines - bufh + 1; 1613 sel->line += arg->i; 1614 if(sel->line < 1) 1615 sel->line = 1; 1616 else if(sel->line > sel->nlines - bufh) 1617 sel->line = 0; 1618 sel->lnoff = bufinfo(sel->data, sel->len, sel->line, LineToOffset); 1619 sel->need_redraw |= (REDRAW_BUFFER | REDRAW_BAR); 1620 } 1621 1622 void 1623 sendident(void) { 1624 sout("NICK %s", nick); 1625 sout("USER %s localhost %s :%s", nick, host, nick); 1626 } 1627 1628 void 1629 setup(void) { 1630 struct termios ti; 1631 struct sigaction sa; 1632 struct winsize ws; 1633 1634 /* clean up any zombies immediately */ 1635 sigchld(0); 1636 1637 setlocale(LC_CTYPE, ""); 1638 sa.sa_flags = 0; 1639 sigemptyset(&sa.sa_mask); 1640 sa.sa_handler = sigwinch; 1641 sigaction(SIGWINCH, &sa, NULL); 1642 tcgetattr(0, &origti); 1643 cfmakeraw(&ti); 1644 ti.c_iflag |= ICRNL; 1645 ti.c_cc[VMIN] = 0; 1646 ti.c_cc[VTIME] = 0; 1647 tcsetattr(0, TCSAFLUSH, &ti); 1648 ioctl(0, TIOCGWINSZ, &ws); 1649 resize(ws.ws_row, ws.ws_col); 1650 } 1651 1652 void 1653 sigchld(int unused) { 1654 if (signal(SIGCHLD, sigchld) == SIG_ERR) 1655 die("can't install SIGCHLD handler:"); 1656 while(0 < waitpid(-1, NULL, WNOHANG)); 1657 } 1658 1659 void 1660 sigwinch(int unused) { 1661 struct winsize ws; 1662 1663 ioctl(0, TIOCGWINSZ, &ws); 1664 resize(ws.ws_row, ws.ws_col); 1665 sel->need_redraw = REDRAW_ALL; 1666 printf(CLEAR); 1667 draw(); 1668 } 1669 1670 char * 1671 skip(char *s, char c) { 1672 while(*s != c && *s != '\0') 1673 s++; 1674 if(*s != '\0') 1675 *s++ = '\0'; 1676 return s; 1677 } 1678 1679 void 1680 sout(char *fmt, ...) { 1681 va_list ap; 1682 1683 va_start(ap, fmt); 1684 vsnprintf(bufout, sizeof bufout, fmt, ap); 1685 va_end(ap); 1686 fprintf(srv, "%s\r\n", bufout); 1687 #ifdef DEBUG 1688 logfmt("%ld DEBUG sout() %s\n", time(NULL), bufout); 1689 #endif 1690 } 1691 1692 void 1693 spawn(const char **cmd) { 1694 if(fork() == 0) { 1695 setsid(); 1696 execvp(cmd[0], (char **)cmd); 1697 fprintf(stderr, "%s: execvp %s", argv0, cmd[0]); 1698 perror(" failed"); 1699 exit(0); 1700 } 1701 } 1702 1703 /* https://modern.ircdocs.horse/formatting.html */ 1704 void 1705 stripformats(char *s) { 1706 char *p = s; 1707 1708 while(*p) { 1709 switch(*p) { 1710 case 0x02: /* bold */ 1711 case 0x1D: /* italic */ 1712 case 0x1F: /* underline */ 1713 case 0x1E: /* strikethrough */ 1714 case 0x11: /* monospace */ 1715 case 0x16: /* reverse */ 1716 case 0x0F: /* reset */ 1717 ++p; 1718 break; 1719 case 0x03: /* colors */ 1720 if(isdigit(*++p) && isdigit(*++p)) 1721 ++p; 1722 if(*p == ',' && isdigit(*++p) && isdigit(*++p)) 1723 ++p; 1724 break; 1725 default: 1726 *s++ = *p++; 1727 break; 1728 } 1729 } 1730 *s++ = *p; 1731 } 1732 1733 void 1734 trim(char *s) { 1735 char *e; 1736 1737 e = s + strlen(s) - 1; 1738 while(isspace(*e) && e > s) 1739 e--; 1740 *(e + 1) = '\0'; 1741 } 1742 1743 int 1744 uiset(char *buf, int index) { 1745 int *color, i, len = 0, n; 1746 char *p; 1747 1748 if(index == -1) 1749 return buf ? sprintf(buf, COLRST) : printf(COLRST); 1750 color = colors[index]; 1751 if(!color) 1752 return -1; 1753 for(i = 0; color[i] != -1; ++i) { 1754 switch(i) { 1755 case 0: p = COLFG; break; 1756 case 1: p = COLBG; break; 1757 default: p = ATTR; break; 1758 } 1759 n = buf ? sprintf(&buf[len], p, color[i]) : printf(p, color[i]); 1760 if(n < 0) 1761 return -1; 1762 len += n; 1763 } 1764 return len; 1765 } 1766 1767 void 1768 usage(void) { 1769 die("Usage: %s [-v] [-hpnl <arg>]", argv0); 1770 } 1771 1772 void 1773 usrin(void) { 1774 char graph[4]; 1775 int key, nb, i; 1776 1777 key = getkey(); 1778 for(i = 0; i < LENGTH(keys); ++i) { 1779 if(keys[i].key == key) { 1780 keys[i].func(&keys[i].arg); 1781 return; 1782 } 1783 } 1784 1785 if(key >= KeyFirst && key <= KeyLast) 1786 return; 1787 if(iscntrl(key)) { 1788 while((key = readchar()) != EOF); 1789 return; 1790 } 1791 1792 nb = UTF8BYTES(key); 1793 graph[0] = key; 1794 for(i = 1; i < nb; ++i) { 1795 key = readchar(); 1796 if(key == EOF) { 1797 /* TODO: preserve the state and return */ 1798 while((key = readchar()) == EOF); 1799 } 1800 graph[i] = key; 1801 } 1802 1803 /* prevent overflow */ 1804 if(sel->cmdlen + nb >= sizeof sel->cmdbuf) 1805 return; 1806 1807 /* move nb bytes to the right */ 1808 memmove(&sel->cmdbuf[sel->cmdoff+nb], 1809 &sel->cmdbuf[sel->cmdoff], 1810 sel->cmdlen - sel->cmdoff); 1811 1812 /* insert nb bytes at current offset */ 1813 memcpy(&sel->cmdbuf[sel->cmdoff], graph, nb); 1814 1815 sel->cmdlen += nb; 1816 sel->cmdbuf[sel->cmdlen] = '\0'; 1817 sel->cmdoff += nb; 1818 1819 sel->need_redraw |= REDRAW_CMDLN; 1820 } 1821 1822 char * 1823 wordleft(char *str, int offset, int *size) { 1824 char *s = &str[offset], *e; 1825 1826 while(s != str && (*s == ' ' || *s == '\0')) 1827 --s; 1828 if(!*s || *s == ' ') 1829 return NULL; 1830 while(s != str && *(s - 1) != ' ') 1831 --s; 1832 if(size) { 1833 for(e = s + 1; *e != '\0' && *e != ' '; ++e); 1834 *size = e - s; 1835 } 1836 return s; 1837 } 1838 1839 int 1840 main(int argc, char *argv[]) { 1841 const char *user = getenv("USER"); 1842 1843 ARGBEGIN { 1844 case 'h': strncpy(host, EARGF(usage()), sizeof host); break; 1845 case 'p': strncpy(port, EARGF(usage()), sizeof port); break; 1846 case 'n': strncpy(nick, EARGF(usage()), sizeof nick); break; 1847 case 'l': strncpy(logfile, EARGF(usage()), sizeof logfile); break; 1848 case 'v': die("circo-"VERSION); 1849 default: usage(); 1850 } ARGEND; 1851 1852 if(!*nick) 1853 strncpy(nick, user ? user : "circo", sizeof nick); 1854 setup(); 1855 if(*logfile) 1856 logp = fopen(logfile, "a"); 1857 setbuf(stdout, NULL); 1858 sel = status = newbuf("status"); 1859 printf(CLEAR); 1860 draw(); 1861 run(); 1862 mvprintf(1, rows, "\n"); 1863 cleanup(); 1864 return 0; 1865 }