ui.c (26711B)
1 /* 2 * src/ui.c from hirc 3 * 4 * Copyright (c) 2021-2022 hhvn <dev@hhvn.uk> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 * 18 */ 19 20 #include <errno.h> 21 #include <ctype.h> 22 #include <wchar.h> 23 #include <wctype.h> 24 #include <stdarg.h> 25 #include <string.h> 26 #include <stdlib.h> 27 #include <locale.h> 28 #include <ncurses.h> 29 #ifdef TLS 30 #include <tls.h> 31 #endif /* TLS */ 32 #include "hirc.h" 33 #include "data/colours.h" 34 35 int uineedredraw = 0; 36 int nouich = 0; 37 38 struct Window windows[Win_last] = { 39 [Win_dummy] = {.handler = NULL, .scroll = -1}, 40 [Win_main] = {.handler = ui_draw_main, .scroll = -1}, 41 [Win_input] = {.handler = ui_draw_input, .scroll = -1}, 42 [Win_nicklist] = {.handler = ui_draw_nicklist, .scroll = -1}, 43 [Win_buflist] = {.handler = ui_draw_buflist, .scroll = -1}, 44 }; 45 46 struct { 47 wchar_t string[INPUT_MAX]; 48 unsigned counter; 49 char *history[INPUT_HIST_MAX]; 50 int histindex; 51 } input; 52 53 struct Selected selected; 54 struct Keybind *keybinds = NULL; 55 56 void 57 ui_error_(char *file, int line, const char *func, char *fmt, ...) { 58 char msg[1024]; 59 va_list ap; 60 61 va_start(ap, fmt); 62 vsnprintf(msg, sizeof(msg), fmt, ap); 63 va_end(ap); 64 65 hist_format(selected.history, Activity_error, HIST_UI|HIST_ERR|HIST_NIGN, 66 "SELF_ERROR %s %d %s :%s", 67 file, line, func, msg); 68 } 69 70 void 71 ui_perror_(char *file, int line, const char *func, char *str) { 72 hist_format(selected.history, Activity_error, HIST_UI|HIST_ERR|HIST_NIGN, 73 "SELF_ERROR %s %d %s :%s: %s", 74 file, line, func, str, strerror(errno)); 75 } 76 77 #ifdef TLS 78 void 79 ui_tls_config_error_(char *file, int line, const char *func, struct tls_config *config, char *str) { 80 hist_format(selected.history, Activity_error, HIST_UI|HIST_ERR|HIST_NIGN, 81 "SELF_ERROR %s %d %s :%s: %s", 82 file, line, func, str, tls_config_error(config)); 83 } 84 85 void 86 ui_tls_error_(char *file, int line, const char *func, struct tls *ctx, char *str) { 87 hist_format(selected.history, Activity_error, HIST_UI|HIST_ERR|HIST_NIGN, 88 "SELF_ERROR %s %d %s :%s: %s", 89 file, line, func, str, tls_error(ctx)); 90 } 91 #endif /* TLS */ 92 93 void 94 ui_init(void) { 95 setlocale(LC_ALL, "en_US.UTF-8"); 96 initscr(); 97 start_color(); 98 use_default_colors(); 99 raw(); 100 noecho(); 101 nonl(); /* get ^j */ 102 103 input.string[0] = L'\0'; 104 memset(input.history, 0, sizeof(input.history)); 105 input.counter = 0; 106 input.histindex = -1; 107 108 windows[Win_nicklist].location = config_getl("nicklist.location"); 109 windows[Win_buflist].location = config_getl("buflist.location"); 110 111 windows[Win_dummy].window = stdscr; 112 windows[Win_main].window = newwin(0, 0, 0, 0); 113 windows[Win_input].window = newwin(0, 0, 0, 0); 114 115 windows[Win_dummy].location = Location_hidden; 116 windows[Win_main].location = -1; 117 windows[Win_input].location = -1; 118 if (windows[Win_nicklist].location) 119 windows[Win_nicklist].window = newwin(0, 0, 0, 0); 120 if (windows[Win_buflist].location) 121 windows[Win_buflist].window = newwin(0, 0, 0, 0); 122 123 nodelay(windows[Win_input].window, TRUE); 124 keypad(windows[Win_input].window, TRUE); 125 126 ui_redraw(); 127 ui_select(NULL, NULL); 128 } 129 130 int 131 ui_get_pair(short fg, short bg) { 132 static unsigned short pair_map[HIRC_COLOURS][HIRC_COLOURS]; 133 static int needinit = 1; 134 short j, k; 135 int i; 136 137 if (needinit) { 138 init_pair(1, -1, -1); 139 for (i=2, j=0; j < HIRC_COLOURS; j++) { 140 for (k=0; k < HIRC_COLOURS; k++) { 141 init_pair(i, colourmap[j], colourmap[k]); 142 pair_map[j][k] = i; 143 i++; 144 } 145 } 146 needinit = 0; 147 } 148 149 if (fg >= HIRC_COLOURS || bg >= HIRC_COLOURS) 150 return 1; 151 152 return pair_map[fg][bg]; 153 } 154 155 void 156 ui_placewindow(struct Window *window) { 157 if (window->location != Location_hidden) { 158 wresize(window->window, window->h, window->w); 159 mvwin(window->window, window->y, window->x); 160 wrefresh(window->window); 161 } 162 } 163 164 void 165 ui_read(void) { 166 static wchar_t *backup = NULL; 167 static int didcomplete = 0; 168 struct Keybind *kp; 169 char *str; 170 wint_t key; 171 int ret; 172 int savecounter; 173 174 savecounter = input.counter; 175 176 /* Loop over input, return only if ERR is received. 177 * Normally wget_wch exits fast enough that unless something 178 * is being pasted in this won't waste any time that should 179 * be used for other stuff */ 180 for (;;) { 181 ret = wget_wch(windows[Win_input].window, &key); 182 if (ret == ERR) { 183 /* no input received */ 184 /* Match keybinds here - this allows multikey 185 * bindings such as those with alt, but since 186 * there is no delay with wgetch() it's unlikely 187 * that the user pressing multiple keys will 188 * trigger one. */ 189 if (input.counter != savecounter) { 190 for (kp = keybinds; kp; kp = kp->next) { 191 if ((input.counter - savecounter) == wcslen(kp->wbinding) && 192 wcsncmp(kp->wbinding, &input.string[savecounter], (input.counter - savecounter)) == 0) { 193 command_eval(selected.server, kp->cmd); 194 memmove(&input.string[savecounter], 195 &input.string[input.counter], 196 (wcslen(&input.string[input.counter]) + 1) * sizeof(wchar_t)); 197 input.counter = savecounter; 198 return; 199 } 200 } 201 202 if (input.histindex) { 203 pfree(&backup); 204 backup = NULL; 205 input.histindex = -1; 206 } 207 } 208 209 windows[Win_input].handler(); 210 wrefresh(windows[Win_input].window); 211 windows[Win_input].refresh = 0; 212 return; 213 } 214 215 switch (key) { 216 case KEY_RESIZE: 217 ui_redraw(); 218 break; 219 case KEY_BACKSPACE: 220 if (input.counter) { 221 memmove(input.string + input.counter - 1, 222 input.string + input.counter, 223 (wcslen(input.string + input.counter) + 1) * sizeof(wchar_t)); 224 input.counter--; 225 } 226 break; 227 case KEY_UP: 228 if (input.histindex < INPUT_HIST_MAX && input.history[input.histindex + 1]) { 229 if (input.histindex == -1) 230 backup = ewcsdup(input.string); 231 input.histindex++; 232 mbstowcs(input.string, input.history[input.histindex], sizeof(input.string)); 233 input.counter = wcslen(input.string); 234 } 235 return; /* return so histindex and backup aren't reset */ 236 case KEY_DOWN: 237 if (input.histindex > -1) { 238 input.histindex--; 239 if (input.histindex == -1) { 240 if (backup) 241 wcslcpy(input.string, backup, sizeof(input.string)); 242 pfree(&backup); 243 backup = NULL; 244 } else { 245 mbstowcs(input.string, input.history[input.histindex], sizeof(input.string)); 246 } 247 input.counter = wcslen(input.string); 248 } 249 return; /* return so histindex and backup aren't reset */ 250 case KEY_LEFT: 251 if (input.counter) 252 input.counter--; 253 break; 254 case KEY_RIGHT: 255 if (input.string[input.counter]) 256 input.counter++; 257 break; 258 case '\t': 259 complete(input.string, sizeof(input.string), &input.counter); 260 break; 261 case KEY_ENTER: 262 case '\r': 263 if (didcomplete && input.string[input.counter - 1] == L' ') 264 input.string[input.counter - 1] = '\0'; 265 if (*input.string != L'\0') { 266 /* no need to free str as assigned to input.history[0] */ 267 str = wctos(input.string); 268 command_eval(selected.server, str); 269 pfree(&input.history[INPUT_HIST_MAX - 1]); 270 memmove(input.history + 1, input.history, (sizeof(input.history) / INPUT_HIST_MAX) * (INPUT_HIST_MAX - 1)); 271 input.history[0] = str; 272 input.string[0] = '\0'; 273 input.counter = 0; 274 input.histindex = -1; 275 } 276 break; 277 default: 278 if (iswprint(key) || (iscntrl(key) && input.counter + 1 != sizeof(input.string))) { 279 memmove(input.string + input.counter + 1, 280 input.string + input.counter, 281 (wcslen(input.string + input.counter) + 1) * sizeof(wchar_t)); 282 input.string[input.counter++] = (wchar_t)key; 283 } 284 break; 285 } 286 287 /* Completion adds a space after the completed text: this 288 * serves the purpose of showing the user that it has been 289 * completely successfully and unambiguously, and allowing them 290 * to begin writing quickly without having to type a space 291 * (this is common in most tab-completion systems). 292 * 293 * The problem is that if something is completed at the end of 294 * a command, and the user then hits enter, that space would be 295 * passed to command_* functions (previous versions of hirc did 296 * this, and put the onus on commands to strip trailing 297 * spaces). 298 * 299 * Instead, this variable is set so that trailing spaces will 300 * be stripped when tab completed, but not ones inserted 301 * manually, inside ui_read() - see the KEY_ENTER case. */ 302 didcomplete = (key == '\t'); 303 } 304 } 305 306 void 307 ui_redraw(void) { 308 struct History *p; 309 char *fmt; 310 long nicklistwidth, buflistwidth; 311 int x = 0, rx = 0; 312 int i; 313 314 nicklistwidth = config_getl("nicklist.width"); 315 buflistwidth = config_getl("buflist.width"); 316 317 /* TODO: what if nicklistwidth or buflistwidth is too big? */ 318 if (windows[Win_buflist].location == Location_left) { 319 windows[Win_buflist].x = windows[Win_buflist].y = 0; 320 windows[Win_buflist].h = LINES; 321 windows[Win_buflist].w = buflistwidth; 322 x = windows[Win_buflist].w + 1; 323 } 324 if (windows[Win_nicklist].location == Location_left) { 325 windows[Win_nicklist].x = windows[Win_buflist].y = 0; 326 windows[Win_nicklist].h = LINES; 327 windows[Win_nicklist].w = nicklistwidth; 328 x = windows[Win_nicklist].w + 1; 329 } 330 if (windows[Win_buflist].location == Location_right) { 331 windows[Win_buflist].x = COLS - buflistwidth; 332 windows[Win_buflist].y = 0; 333 windows[Win_buflist].h = LINES; 334 windows[Win_buflist].w = buflistwidth; 335 rx = buflistwidth + 1; 336 } 337 if (windows[Win_nicklist].location == Location_right) { 338 windows[Win_nicklist].x = COLS - nicklistwidth; 339 windows[Win_nicklist].y = 0; 340 windows[Win_nicklist].h = LINES; 341 windows[Win_nicklist].w = nicklistwidth; 342 rx = nicklistwidth + 1; 343 } 344 345 windows[Win_main].x = x; 346 windows[Win_main].y = 0; 347 windows[Win_main].h = LINES - 2; 348 windows[Win_main].w = COLS - x - rx; 349 350 windows[Win_input].x = x; 351 windows[Win_input].y = LINES - 1; 352 windows[Win_input].h = 1; 353 windows[Win_input].w = COLS - x - rx; 354 355 windows[Win_dummy].x = 0; 356 windows[Win_dummy].y = 0; 357 windows[Win_dummy].h = LINES; 358 windows[Win_dummy].w = COLS; 359 360 fmt = format(NULL, config_gets("format.ui.separator.horizontal"), NULL); 361 for (i = x; i <= COLS - rx; i++) { 362 wmove(windows[Win_dummy].window, LINES - 2, i); 363 ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); 364 } 365 366 if (x) { 367 fmt = format(NULL, config_gets("format.ui.separator.vertical"), NULL); 368 for (i = 0; i <= LINES; i++) { 369 wmove(windows[Win_dummy].window, i, x - 1); 370 ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); 371 } 372 373 fmt = format(NULL, config_gets("format.ui.separator.split.left"), NULL); 374 wmove(windows[Win_dummy].window, LINES - 2, x - 1); 375 ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); 376 } 377 378 if (rx) { 379 fmt = format(NULL, config_gets("format.ui.separator.vertical"), NULL); 380 for (i = 0; i <= LINES; i++) { 381 wmove(windows[Win_dummy].window, i, COLS - rx); 382 ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); 383 } 384 385 fmt = format(NULL, config_gets("format.ui.separator.split.right"), NULL); 386 wmove(windows[Win_dummy].window, LINES - 2, COLS - rx); 387 ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); 388 } 389 390 refresh(); 391 392 for (i = 0; i < Win_last; i++) { 393 if (windows[i].location) { 394 ui_placewindow(&windows[i]); 395 windows[i].refresh = 1; 396 } 397 } 398 399 /* Clear format element of history. 400 * Formats need updating if the windows are resized, 401 * or format.* settings are changed. */ 402 if (selected.history) { 403 for (p = selected.history->history; p; p = p->next) { 404 if (p->format) 405 pfree(&p->format); 406 if (p->rformat) 407 pfree(&p->rformat); 408 } 409 } 410 } 411 412 void 413 ui_draw_input(void) { 414 wchar_t *p; 415 int offset; 416 int x; 417 418 werase(windows[Win_input].window); 419 420 /* Round input.counter down to the nearest windows[Win_input].w. 421 * This gives "pages" that are each as long as the width of the input window */ 422 offset = ((int) input.counter / windows[Win_input].w) * windows[Win_input].w; 423 for (x=0, p = input.string + offset; p && *p && x < windows[Win_input].w; p++, x++) { 424 if (iscntrl(*p)) { 425 /* adding 64 will turn ^C into C */ 426 wattron(windows[Win_input].window, A_REVERSE); 427 waddch(windows[Win_input].window, *p + 64); 428 wattroff(windows[Win_input].window, A_REVERSE); 429 } else { 430 waddnwstr(windows[Win_input].window, p, 1); 431 } 432 } 433 wmove(windows[Win_input].window, 0, input.counter - offset); 434 } 435 436 void 437 ui_draw_nicklist(void) { 438 struct Nick *p; 439 int y = 0, i; 440 441 werase(windows[Win_nicklist].window); 442 443 if (!selected.channel || !windows[Win_nicklist].location) 444 return; 445 446 wmove(windows[Win_nicklist].window, 0, 0); 447 448 nick_sort(&selected.channel->nicks, selected.server); 449 450 for (i=0, p = selected.channel->nicks; p && p->next && p->next->next && i < windows[Win_nicklist].scroll; i++) 451 p = p->next; 452 if (i != 0) { 453 ui_wprintc(&windows[Win_nicklist], 1, "%s\n", format(NULL, config_gets("format.ui.nicklist.more"), NULL)); 454 y++; 455 p = p->next; 456 windows[Win_nicklist].scroll = i; 457 } 458 459 for (; p && y < windows[Win_nicklist].h - (p->next ? 1 : 0); p = p->next, y++) { 460 ui_wprintc(&windows[Win_nicklist], 1, "%c%02d%c%s\n", 461 3 /* ^C */, nick_getcolour(p), p->priv, p->nick); 462 } 463 464 if (p) 465 ui_wprintc(&windows[Win_nicklist], 1, "%s\n", format(NULL, config_gets("format.ui.nicklist.more"), NULL)); 466 } 467 468 int 469 ui_buflist_count(int *ret_servers, int *ret_channels, int *ret_queries) { 470 struct Server *sp; 471 struct Channel *chp; 472 int sc, cc, pc; 473 474 for (sc = cc = pc = 0, sp = servers; sp; sp = sp->next, sc++) { 475 for (chp = sp->channels; chp; chp = chp->next, cc++) 476 ; 477 for (chp = sp->queries; chp; chp = chp->next, pc++) 478 ; 479 } 480 481 if (ret_servers) 482 *ret_servers = sc; 483 if (ret_channels) 484 *ret_channels = cc; 485 if (ret_queries) 486 *ret_channels = pc; 487 488 return sc + cc + pc + 1; 489 } 490 491 int 492 ui_buflist_get(int num, struct Server **server, struct Channel **chan) { 493 struct Server *sp; 494 struct Channel *chp; 495 int i; 496 497 if (num <= 0) { 498 ui_error("buffer index greater than 0 expected", NULL); 499 return -1; 500 } 501 502 if (num == 1) { 503 *server = NULL; 504 *chan = NULL; 505 return 0; 506 } 507 508 for (i = 2, sp = servers; sp; sp = sp->next) { 509 if (i == num) { 510 *server = sp; 511 *chan = NULL; 512 return 0; 513 } 514 i++; /* increment before moving 515 to channel section, not 516 int for (;; ..) */ 517 518 for (chp = sp->channels; chp; chp = chp->next, i++) { 519 if (i == num) { 520 *server = sp; 521 *chan = chp; 522 return 0; 523 } 524 } 525 for (chp = sp->queries; chp; chp = chp->next, i++) { 526 if (i == num) { 527 *server = sp; 528 *chan = chp; 529 return 0; 530 } 531 } 532 } 533 534 ui_error("couldn't find buffer with index %d", num); 535 return -1; 536 } 537 538 void 539 ui_draw_buflist(void) { 540 struct Server *sp; 541 struct Channel *chp, *prp; 542 int i = 1, scroll; 543 char *actind[Activity_last]; 544 char *oldind, *indicator; 545 546 oldind = estrdup(format(NULL, config_gets("format.ui.buflist.old"), NULL)); 547 actind[Activity_none] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.none"), NULL)); 548 actind[Activity_status] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.status"), NULL)); 549 actind[Activity_error] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.error"), NULL)); 550 actind[Activity_message] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.message"), NULL)); 551 actind[Activity_hilight] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.hilight"), NULL)); 552 553 werase(windows[Win_buflist].window); 554 555 if (windows[Win_buflist].scroll < 0) 556 scroll = 0; 557 else 558 scroll = windows[Win_buflist].scroll; 559 560 if (!windows[Win_buflist].location) 561 return; 562 563 if (scroll > 0) { 564 ui_wprintc(&windows[Win_buflist], 1, "%s\n", format(NULL, config_gets("format.ui.buflist.more"), NULL)); 565 } else if (scroll < i) { 566 if (selected.history == main_buf) 567 wattron(windows[Win_buflist].window, A_BOLD); 568 ui_wprintc(&windows[Win_buflist], 1, "%02d: %s\n", i, "hirc"); 569 wattroff(windows[Win_buflist].window, A_BOLD); 570 } 571 i++; 572 573 for (sp = servers; sp && (i - scroll - 1) < windows[Win_buflist].h; sp = sp->next) { 574 if (scroll < i - 1) { 575 if (selected.server == sp && !selected.channel) 576 wattron(windows[Win_buflist].window, A_BOLD); 577 indicator = (sp->status == ConnStatus_notconnected) ? oldind : actind[sp->history->activity]; 578 ui_wprintc(&windows[Win_buflist], 1, "%02d: %s─ %s%s\n", i, sp->next ? "├" : "└", indicator, sp->name); 579 wattrset(windows[Win_buflist].window, A_NORMAL); 580 } 581 i++; 582 583 for (chp = sp->channels; chp && (i - scroll - 1) < windows[Win_buflist].h; chp = chp->next) { 584 if (scroll < i - 1) { 585 if (selected.channel == chp) 586 wattron(windows[Win_buflist].window, A_BOLD); 587 indicator = (chp->old) ? oldind : actind[chp->history->activity]; 588 ui_wprintc(&windows[Win_buflist], 1, "%02d: %s %s─ %s%s\n", i, 589 sp->next ? "│" : " ", chp->next || sp->queries ? "├" : "└", indicator, chp->name); 590 wattrset(windows[Win_buflist].window, A_NORMAL); 591 } 592 i++; 593 } 594 595 for (prp = sp->queries; prp && (i - scroll - 1) < windows[Win_buflist].h; prp = prp->next) { 596 if (scroll < i - 1) { 597 if (selected.channel == prp) 598 wattron(windows[Win_buflist].window, A_BOLD); 599 indicator = (prp->old) ? oldind : actind[prp->history->activity]; 600 ui_wprintc(&windows[Win_buflist], 1, "%02d: %s %s─ %s%s\n", i, 601 sp->next ? "│" : " ", prp->next ? "├" : "└", indicator, prp->name); 602 wattrset(windows[Win_buflist].window, A_NORMAL); 603 } 604 i++; 605 } 606 } 607 608 if (i <= ui_buflist_count(NULL, NULL, NULL)) { 609 wmove(windows[Win_buflist].window, windows[Win_buflist].h - 1, 0); 610 ui_wprintc(&windows[Win_buflist], 1, "%s\n", format(NULL, config_gets("format.ui.buflist.more"), NULL)); 611 wclrtoeol(windows[Win_buflist].window); 612 } 613 } 614 615 int 616 ui_wprintc(struct Window *window, int lines, char *fmt, ...) { 617 char *str; 618 wchar_t *wcs, *s; 619 va_list ap; 620 int ret; 621 attr_t curattr; 622 short temp; /* used only for wattr_get, 623 because ncurses is dumb */ 624 int cc, lc, elc; 625 char colourbuf[2][3]; 626 int colours[2]; 627 int bold = 0; 628 int underline = 0; 629 int reverse = 0; 630 int italic = 0; 631 632 va_start(ap, fmt); 633 ret = vsnprintf(str, 0, fmt, ap) + 1; 634 va_end(ap); 635 str = emalloc(ret); 636 637 va_start(ap, fmt); 638 ret = vsnprintf(str, ret, fmt, ap); 639 va_end(ap); 640 if (ret < 0) 641 return ret; 642 643 if (lines < 0) 644 ui_strlenc(window, str, &elc); 645 elc -= 1; 646 647 wcs = stowc(str); 648 pfree(&str); 649 650 for (ret = cc = lc = 0, s = wcs; s && *s; s++) { 651 switch (*s) { 652 case 2: /* ^B */ 653 if (bold) 654 wattroff(window->window, A_BOLD); 655 else 656 wattron(window->window, A_BOLD); 657 bold = bold ? 0 : 1; 658 break; 659 case 3: /* ^C */ 660 memset(colourbuf, '\0', sizeof(colourbuf)); 661 /* This section may look a little confusing, but I didn't know 662 * what better way I could do it (a loop for two things? ehm). 663 * 664 * If you want to understand it, I would start with simulating 665 * it on a peice of paper, something like this: 666 * 667 * { , ,'\0'} ^C01,02 668 * { , ,'\0'} 669 * 670 * Draw a line over *s each time you advance s. */ 671 if (*s && isdigit(*(s+1))) { 672 colourbuf[0][0] = *(s+1); 673 s += 1; 674 } 675 if (*s && isdigit(*(s+1))) { 676 colourbuf[0][1] = *(s+1); 677 s += 1; 678 } 679 if (*s && *(s+1) == ',' && isdigit(*(s+2))) { 680 colourbuf[1][0] = *(s+2); 681 s += 2; 682 } 683 if (colourbuf[1][0] && *s && isdigit(*(s+1))) { 684 colourbuf[1][1] = *(s+1); 685 s += 1; 686 } 687 688 colours[0] = colourbuf[0][0] ? atoi(colourbuf[0]) : 99; 689 colours[1] = colourbuf[1][0] ? atoi(colourbuf[1]) : 99; 690 691 wattr_get(window->window, &curattr, &temp, NULL); 692 wattr_set(window->window, curattr, ui_get_pair(colours[0], colours[1]), NULL); 693 break; 694 case 9: /* ^I */ 695 #ifdef A_ITALIC 696 if (italic) 697 wattroff(window->window, A_ITALIC); 698 else 699 wattron(window->window, A_ITALIC); 700 italic = italic ? 0 : 1; 701 #endif /* A_ITALIC */ 702 break; 703 case 15: /* ^O */ 704 bold = 0; 705 underline = 0; 706 reverse = 0; 707 italic = 0; 708 /* Setting A_NORMAL turns everything off, 709 * without using 5 different attroffs */ 710 wattrset(window->window, A_NORMAL); 711 break; 712 case 18: /* ^R */ 713 if (reverse) 714 wattroff(window->window, A_REVERSE); 715 else 716 wattron(window->window, A_REVERSE); 717 reverse = reverse ? 0 : 1; 718 break; 719 case 21: /* ^U */ 720 if (underline) 721 wattroff(window->window, A_UNDERLINE); 722 else 723 wattron(window->window, A_UNDERLINE); 724 underline = underline ? 0 : 1; 725 break; 726 default: 727 if (lines > 0 && lc >= lines) 728 goto end; 729 if (!lines || lines > 0 || (lines < 0 && lc >= elc + lines)) { 730 waddnwstr(window->window, s, 1); 731 ret++; 732 cc++; 733 } 734 if (cc == window->w || *s == L'\n') { 735 lc++; 736 cc = 0; 737 } 738 break; 739 } 740 } 741 742 end: 743 pfree(&wcs); 744 bold = 0; 745 underline =0; 746 reverse = 0; 747 italic = 0; 748 wattrset(window->window, A_NORMAL); 749 750 return ret; 751 } 752 753 int 754 ui_strlenc(struct Window *window, char *s, int *lines) { 755 int ret, cc, lc; 756 757 for (ret = cc = lc = 0; s && *s; s++) { 758 switch (*s) { 759 case 2: /* ^B */ 760 case 9: /* ^I */ 761 case 15: /* ^O */ 762 case 18: /* ^R */ 763 case 21: /* ^U */ 764 break; 765 case 3: /* ^C */ 766 if (*s && isdigit(*(s+1))) 767 s += 1; 768 if (*s && isdigit(*(s+1))) 769 s += 1; 770 if (*s && *(s+1) == ',' && isdigit(*(s+2))) 771 s += 2; 772 if (*s && *(s-1) == ',' && isdigit(*(s+1))) 773 s += 1; 774 break; 775 default: 776 /* Naive utf-8 handling: the 2-nth byte always follows 777 * 10xxxxxxx, so don't count it. 778 * 779 * I figured it would be better to do it this way than 780 * to use widechars, as then there would need to be a 781 * conversion for each call. */ 782 if ((*s & 0xC0) != 0x80) 783 cc++; 784 ret++; 785 if ((window && cc == window->w) || *s == '\n') { 786 lc++; 787 cc = 0; 788 } 789 break; 790 } 791 } 792 793 if (lines) 794 *lines = lc + 1; 795 return ret; 796 } 797 798 void 799 ui_draw_main(void) { 800 struct History *p, *hp; 801 int y, lines; 802 int i; 803 804 werase(windows[Win_main].window); 805 806 for (i=0, p = selected.history->history, hp = NULL; p; p = p->next) { 807 if (!(p->options & HIST_SHOW) || ((p->options & HIST_IGN) && !selected.showign)) 808 continue; 809 if (windows[Win_main].scroll > 0 && !hp && !p->next) 810 hp = p; 811 else if (i == windows[Win_main].scroll && !hp) 812 hp = p; 813 814 if (i < windows[Win_main].scroll) 815 i++; 816 if (!p->format) 817 p->format = estrdup(format(&windows[Win_main], NULL, p)); 818 } 819 820 if (windows[Win_main].scroll > 0) 821 windows[Win_main].scroll = i; 822 if (!hp) 823 hp = selected.history->history; 824 825 for (y = windows[Win_main].h; hp && y > 0; hp = hp->next) { 826 if (!(hp->options & HIST_SHOW) || !hp->format || ((hp->options & HIST_IGN) && !selected.showign)) 827 continue; 828 if (ui_strlenc(&windows[Win_main], hp->format, &lines) <= 0) 829 continue; 830 y = y - lines; 831 if (y < lines) { 832 y *= -1; 833 wmove(windows[Win_main].window, 0, 0); 834 ui_wprintc(&windows[Win_main], y, "%s\n", hp->format); 835 break; 836 } 837 wmove(windows[Win_main].window, y, 0); 838 ui_wprintc(&windows[Win_main], 0, "%s\n", hp->format); 839 } 840 841 if (selected.channel && selected.channel->topic) { 842 wmove(windows[Win_main].window, 0, 0); 843 ui_wprintc(&windows[Win_main], 0, "%s\n", format(&windows[Win_main], config_gets("format.ui.topic"), NULL)); 844 } 845 } 846 847 void 848 ui_select(struct Server *server, struct Channel *channel) { 849 struct History *hp, *ind; 850 int i, total; 851 852 if (selected.history) 853 hist_purgeopt(selected.history, HIST_TMP); 854 855 selected.channel = channel; 856 selected.server = server; 857 selected.history = channel ? channel->history : server ? server->history : main_buf; 858 selected.name = channel ? channel->name : server ? server->name : "hirc"; 859 selected.hasnicks = channel ? !channel->query && !channel->old : 0; 860 selected.showign = 0; 861 862 if (selected.history->unread || selected.history->ignored) { 863 for (i = 0, hp = selected.history->history; hp && hp->next; hp = hp->next, i++); 864 if (i == (HIST_MAX-1)) { 865 pfree(&hp->next); 866 hp->next = NULL; 867 } 868 869 total = selected.history->unread + selected.history->ignored; 870 871 for (i = 0, hp = selected.history->history; hp && hp->next && i < total; hp = hp->next) 872 if (hp->options & HIST_SHOW) 873 i++; 874 if (hp) { 875 ind = hist_format(NULL, Activity_none, HIST_SHOW|HIST_TMP, "SELF_UNREAD %d %d :unread, ignored", 876 selected.history->unread, selected.history->ignored); 877 ind->origin = selected.history; 878 ind->next = hp; 879 ind->prev = hp->prev; 880 if (hp->prev) 881 hp->prev->next = ind; 882 hp->prev = ind; 883 } 884 } 885 886 887 selected.history->activity = Activity_none; 888 selected.history->unread = selected.history->ignored = 0; 889 890 if (!selected.hasnicks || config_getl("nicklist.hidden")) 891 windows[Win_nicklist].location = Location_hidden; 892 else 893 windows[Win_nicklist].location = config_getl("nicklist.location"); 894 windows[Win_main].scroll = -1; 895 ui_redraw(); 896 } 897 898 char * 899 ui_rectrl(char *str) { 900 static char ret[8192]; 901 static char *rp = NULL; 902 int caret, rc; 903 char c; 904 905 if (rp) { 906 pfree(&rp); 907 rp = NULL; 908 } 909 910 for (rc = 0, caret = 0; str && *str; str++) { 911 if (caret) { 912 c = toupper(*str) - 64; 913 if (c <= 31 && c >= 0) { 914 ret[rc++] = c; 915 } else { 916 ret[rc++] = '^'; 917 ret[rc++] = *str; 918 } 919 caret = 0; 920 } else if (*str == '^') { 921 caret = 1; 922 } else { 923 ret[rc++] = *str; 924 } 925 } 926 927 if (caret) 928 ret[rc++] = '^'; 929 ret[rc] = '\0'; 930 rp = estrdup(ret); 931 932 return rp; 933 } 934 935 char * 936 ui_unctrl(char *str) { 937 static char ret[8192]; 938 static char *rp = NULL;; 939 int rc; 940 941 if (rp) { 942 pfree(&rp); 943 rp = NULL; 944 } 945 946 for (rc = 0; str && *str; str++) { 947 if (*str <= 31 && *str >= 0) { 948 ret[rc++] = '^'; 949 ret[rc++] = (*str) + 64; 950 } else { 951 ret[rc++] = *str; 952 } 953 } 954 955 ret[rc] = '\0'; 956 rp = estrdup(ret); 957 958 return rp; 959 } 960 961 int 962 ui_bind(char *binding, char *cmd) { 963 struct Keybind *p; 964 char *tmp, *b; 965 966 assert_warn(binding && cmd, -1); 967 b = ui_rectrl(binding); 968 969 for (p = keybinds; p; p = p->next) 970 if (strcmp(p->binding, b) == 0) 971 return -1; 972 973 p = emalloc(sizeof(struct Keybind)); 974 p->binding = estrdup(b); 975 p->wbinding = stowc(p->binding); 976 if (*cmd != '/') { 977 tmp = smprintf(strlen(cmd) + 2, "/%s", cmd); 978 p->cmd = tmp; 979 } else { 980 p->cmd = estrdup(cmd); 981 } 982 p->prev = NULL; 983 p->next = keybinds; 984 if (keybinds) 985 keybinds->prev = p; 986 keybinds = p; 987 988 return 0; 989 } 990 991 int 992 ui_unbind(char *binding) { 993 struct Keybind *p; 994 char *b; 995 996 assert_warn(binding, -1); 997 b = ui_rectrl(binding); 998 999 for (p=keybinds; p; p = p->next) { 1000 if (strcmp(p->binding, b) == 0) { 1001 if (p->prev) 1002 p->prev->next = p->next; 1003 else 1004 keybinds = p->next; 1005 1006 if (p->next) 1007 p->next->prev = p->prev; 1008 1009 pfree(&p->binding); 1010 pfree(&p->wbinding); 1011 pfree(&p->cmd); 1012 pfree(&p); 1013 return 0; 1014 } 1015 } 1016 1017 return -1; 1018 }