status.c revision 1.80
1/* $OpenBSD: status.c,v 1.80 2011/11/15 23:19:51 nicm Exp $ */ 2 3/* 4 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> 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 MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <sys/types.h> 20#include <sys/time.h> 21 22#include <errno.h> 23#include <limits.h> 24#include <stdarg.h> 25#include <stdlib.h> 26#include <string.h> 27#include <time.h> 28#include <unistd.h> 29 30#include "tmux.h" 31 32char *status_redraw_get_left( 33 struct client *, time_t, int, struct grid_cell *, size_t *); 34char *status_redraw_get_right( 35 struct client *, time_t, int, struct grid_cell *, size_t *); 36char *status_find_job(struct client *, char **); 37void status_job_free(void *); 38void status_job_callback(struct job *); 39char *status_print( 40 struct client *, struct winlink *, time_t, struct grid_cell *); 41void status_replace1(struct client *, struct session *, struct winlink *, 42 struct window_pane *, char **, char **, char *, size_t, int); 43void status_message_callback(int, short, void *); 44 45const char *status_prompt_up_history(u_int *); 46const char *status_prompt_down_history(u_int *); 47void status_prompt_add_history(const char *); 48char *status_prompt_complete(const char *); 49 50/* Status prompt history. */ 51ARRAY_DECL(, char *) status_prompt_history = ARRAY_INITIALIZER; 52 53/* Status output tree. */ 54RB_GENERATE(status_out_tree, status_out, entry, status_out_cmp); 55 56/* Output tree comparison function. */ 57int 58status_out_cmp(struct status_out *so1, struct status_out *so2) 59{ 60 return (strcmp(so1->cmd, so2->cmd)); 61} 62 63/* Retrieve options for left string. */ 64char * 65status_redraw_get_left(struct client *c, 66 time_t t, int utf8flag, struct grid_cell *gc, size_t *size) 67{ 68 struct session *s = c->session; 69 char *left; 70 u_char fg, bg, attr; 71 size_t leftlen; 72 73 fg = options_get_number(&s->options, "status-left-fg"); 74 if (fg != 8) 75 colour_set_fg(gc, fg); 76 bg = options_get_number(&s->options, "status-left-bg"); 77 if (bg != 8) 78 colour_set_bg(gc, bg); 79 attr = options_get_number(&s->options, "status-left-attr"); 80 if (attr != 0) 81 gc->attr = attr; 82 83 left = status_replace(c, NULL, 84 NULL, NULL, options_get_string(&s->options, "status-left"), t, 1); 85 86 *size = options_get_number(&s->options, "status-left-length"); 87 leftlen = screen_write_cstrlen(utf8flag, "%s", left); 88 if (leftlen < *size) 89 *size = leftlen; 90 return (left); 91} 92 93/* Retrieve options for right string. */ 94char * 95status_redraw_get_right(struct client *c, 96 time_t t, int utf8flag, struct grid_cell *gc, size_t *size) 97{ 98 struct session *s = c->session; 99 char *right; 100 u_char fg, bg, attr; 101 size_t rightlen; 102 103 fg = options_get_number(&s->options, "status-right-fg"); 104 if (fg != 8) 105 colour_set_fg(gc, fg); 106 bg = options_get_number(&s->options, "status-right-bg"); 107 if (bg != 8) 108 colour_set_bg(gc, bg); 109 attr = options_get_number(&s->options, "status-right-attr"); 110 if (attr != 0) 111 gc->attr = attr; 112 113 right = status_replace(c, NULL, 114 NULL, NULL, options_get_string(&s->options, "status-right"), t, 1); 115 116 *size = options_get_number(&s->options, "status-right-length"); 117 rightlen = screen_write_cstrlen(utf8flag, "%s", right); 118 if (rightlen < *size) 119 *size = rightlen; 120 return (right); 121} 122 123/* Set window at window list position. */ 124void 125status_set_window_at(struct client *c, u_int x) 126{ 127 struct session *s = c->session; 128 struct winlink *wl; 129 130 x += s->wlmouse; 131 RB_FOREACH(wl, winlinks, &s->windows) { 132 if (x < wl->status_width && 133 session_select(s, wl->idx) == 0) { 134 server_redraw_session(s); 135 } 136 x -= wl->status_width + 1; 137 } 138} 139 140/* Draw status for client on the last lines of given context. */ 141int 142status_redraw(struct client *c) 143{ 144 struct screen_write_ctx ctx; 145 struct session *s = c->session; 146 struct winlink *wl; 147 struct screen old_status, window_list; 148 struct grid_cell stdgc, lgc, rgc, gc; 149 time_t t; 150 char *left, *right; 151 u_int offset, needed; 152 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize; 153 size_t llen, rlen; 154 int larrow, rarrow, utf8flag; 155 156 /* No status line? */ 157 if (c->tty.sy == 0 || !options_get_number(&s->options, "status")) 158 return (1); 159 left = right = NULL; 160 larrow = rarrow = 0; 161 162 /* Update status timer. */ 163 if (gettimeofday(&c->status_timer, NULL) != 0) 164 fatal("gettimeofday failed"); 165 t = c->status_timer.tv_sec; 166 167 /* Set up default colour. */ 168 memcpy(&stdgc, &grid_default_cell, sizeof gc); 169 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg")); 170 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg")); 171 stdgc.attr |= options_get_number(&s->options, "status-attr"); 172 173 /* Create the target screen. */ 174 memcpy(&old_status, &c->status, sizeof old_status); 175 screen_init(&c->status, c->tty.sx, 1, 0); 176 screen_write_start(&ctx, NULL, &c->status); 177 for (offset = 0; offset < c->tty.sx; offset++) 178 screen_write_putc(&ctx, &stdgc, ' '); 179 screen_write_stop(&ctx); 180 181 /* If the height is one line, blank status line. */ 182 if (c->tty.sy <= 1) 183 goto out; 184 185 /* Get UTF-8 flag. */ 186 utf8flag = options_get_number(&s->options, "status-utf8"); 187 188 /* Work out left and right strings. */ 189 memcpy(&lgc, &stdgc, sizeof lgc); 190 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen); 191 memcpy(&rgc, &stdgc, sizeof rgc); 192 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen); 193 194 /* 195 * Figure out how much space we have for the window list. If there 196 * isn't enough space, just show a blank status line. 197 */ 198 needed = 0; 199 if (llen != 0) 200 needed += llen + 1; 201 if (rlen != 0) 202 needed += rlen + 1; 203 if (c->tty.sx == 0 || c->tty.sx <= needed) 204 goto out; 205 wlavailable = c->tty.sx - needed; 206 207 /* Calculate the total size needed for the window list. */ 208 wlstart = wloffset = wlwidth = 0; 209 RB_FOREACH(wl, winlinks, &s->windows) { 210 if (wl->status_text != NULL) 211 xfree(wl->status_text); 212 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell); 213 wl->status_text = status_print(c, wl, t, &wl->status_cell); 214 wl->status_width = 215 screen_write_cstrlen(utf8flag, "%s", wl->status_text); 216 217 if (wl == s->curw) 218 wloffset = wlwidth; 219 wlwidth += wl->status_width + 1; 220 } 221 222 /* Create a new screen for the window list. */ 223 screen_init(&window_list, wlwidth, 1, 0); 224 225 /* And draw the window list into it. */ 226 screen_write_start(&ctx, NULL, &window_list); 227 RB_FOREACH(wl, winlinks, &s->windows) { 228 screen_write_cnputs(&ctx, 229 -1, &wl->status_cell, utf8flag, "%s", wl->status_text); 230 screen_write_putc(&ctx, &stdgc, ' '); 231 } 232 screen_write_stop(&ctx); 233 234 /* If there is enough space for the total width, skip to draw now. */ 235 if (wlwidth <= wlavailable) 236 goto draw; 237 238 /* Find size of current window text. */ 239 wlsize = s->curw->status_width; 240 241 /* 242 * If the current window is already on screen, good to draw from the 243 * start and just leave off the end. 244 */ 245 if (wloffset + wlsize < wlavailable) { 246 if (wlavailable > 0) { 247 rarrow = 1; 248 wlavailable--; 249 } 250 wlwidth = wlavailable; 251 } else { 252 /* 253 * Work out how many characters we need to omit from the 254 * start. There are wlavailable characters to fill, and 255 * wloffset + wlsize must be the last. So, the start character 256 * is wloffset + wlsize - wlavailable. 257 */ 258 if (wlavailable > 0) { 259 larrow = 1; 260 wlavailable--; 261 } 262 263 wlstart = wloffset + wlsize - wlavailable; 264 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) { 265 rarrow = 1; 266 wlstart++; 267 wlavailable--; 268 } 269 wlwidth = wlavailable; 270 } 271 272 /* Bail if anything is now too small too. */ 273 if (wlwidth == 0 || wlavailable == 0) { 274 screen_free(&window_list); 275 goto out; 276 } 277 278 /* 279 * Now the start position is known, work out the state of the left and 280 * right arrows. 281 */ 282 offset = 0; 283 RB_FOREACH(wl, winlinks, &s->windows) { 284 if (wl->flags & WINLINK_ALERTFLAGS && 285 larrow == 1 && offset < wlstart) 286 larrow = -1; 287 288 offset += wl->status_width; 289 290 if (wl->flags & WINLINK_ALERTFLAGS && 291 rarrow == 1 && offset > wlstart + wlwidth) 292 rarrow = -1; 293 } 294 295draw: 296 /* Begin drawing. */ 297 screen_write_start(&ctx, NULL, &c->status); 298 299 /* Draw the left string and arrow. */ 300 screen_write_cursormove(&ctx, 0, 0); 301 if (llen != 0) { 302 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left); 303 screen_write_putc(&ctx, &stdgc, ' '); 304 } 305 if (larrow != 0) { 306 memcpy(&gc, &stdgc, sizeof gc); 307 if (larrow == -1) 308 gc.attr ^= GRID_ATTR_REVERSE; 309 screen_write_putc(&ctx, &gc, '<'); 310 } 311 312 /* Draw the right string and arrow. */ 313 if (rarrow != 0) { 314 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0); 315 memcpy(&gc, &stdgc, sizeof gc); 316 if (rarrow == -1) 317 gc.attr ^= GRID_ATTR_REVERSE; 318 screen_write_putc(&ctx, &gc, '>'); 319 } else 320 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0); 321 if (rlen != 0) { 322 screen_write_putc(&ctx, &stdgc, ' '); 323 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right); 324 } 325 326 /* Figure out the offset for the window list. */ 327 if (llen != 0) 328 wloffset = llen + 1; 329 else 330 wloffset = 0; 331 if (wlwidth < wlavailable) { 332 switch (options_get_number(&s->options, "status-justify")) { 333 case 1: /* centered */ 334 wloffset += (wlavailable - wlwidth) / 2; 335 break; 336 case 2: /* right */ 337 wloffset += (wlavailable - wlwidth); 338 break; 339 } 340 } 341 if (larrow != 0) 342 wloffset++; 343 344 /* Copy the window list. */ 345 s->wlmouse = -wloffset + wlstart; 346 screen_write_cursormove(&ctx, wloffset, 0); 347 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1); 348 screen_free(&window_list); 349 350 screen_write_stop(&ctx); 351 352out: 353 if (left != NULL) 354 xfree(left); 355 if (right != NULL) 356 xfree(right); 357 358 if (grid_compare(c->status.grid, old_status.grid) == 0) { 359 screen_free(&old_status); 360 return (0); 361 } 362 screen_free(&old_status); 363 return (1); 364} 365 366/* Replace a single special sequence (prefixed by #). */ 367void 368status_replace1(struct client *c, struct session *s, struct winlink *wl, 369 struct window_pane *wp, char **iptr, char **optr, char *out, 370 size_t outsize, int jobsflag) 371{ 372 char ch, tmp[256], *ptr, *endptr, *freeptr; 373 size_t ptrlen; 374 long limit; 375 u_int idx; 376 377 if (s == NULL) 378 s = c->session; 379 if (wl == NULL) 380 wl = s->curw; 381 if (wp == NULL) 382 wp = wl->window->active; 383 384 errno = 0; 385 limit = strtol(*iptr, &endptr, 10); 386 if ((limit == 0 && errno != EINVAL) || 387 (limit == LONG_MIN && errno != ERANGE) || 388 (limit == LONG_MAX && errno != ERANGE) || 389 limit != 0) 390 *iptr = endptr; 391 if (limit <= 0) 392 limit = LONG_MAX; 393 394 freeptr = NULL; 395 396 switch (*(*iptr)++) { 397 case '(': 398 if (!jobsflag) { 399 ch = ')'; 400 goto skip_to; 401 } 402 if ((ptr = status_find_job(c, iptr)) == NULL) 403 return; 404 goto do_replace; 405 case 'D': 406 xsnprintf(tmp, sizeof tmp, "%%%u", wp->id); 407 ptr = tmp; 408 goto do_replace; 409 case 'H': 410 if (gethostname(tmp, sizeof tmp) != 0) 411 fatal("gethostname failed"); 412 ptr = tmp; 413 goto do_replace; 414 case 'h': 415 if (gethostname(tmp, sizeof tmp) != 0) 416 fatal("gethostname failed"); 417 if ((ptr = strchr(tmp, '.')) != NULL) 418 *ptr = '\0'; 419 ptr = tmp; 420 goto do_replace; 421 case 'I': 422 xsnprintf(tmp, sizeof tmp, "%d", wl->idx); 423 ptr = tmp; 424 goto do_replace; 425 case 'P': 426 if (window_pane_index(wp, &idx) != 0) 427 fatalx("index not found"); 428 xsnprintf( 429 tmp, sizeof tmp, "%u", idx); 430 ptr = tmp; 431 goto do_replace; 432 case 'S': 433 ptr = s->name; 434 goto do_replace; 435 case 'T': 436 ptr = wp->base.title; 437 goto do_replace; 438 case 'W': 439 ptr = wl->window->name; 440 goto do_replace; 441 case 'F': 442 ptr = window_printable_flags(s, wl); 443 freeptr = ptr; 444 goto do_replace; 445 case '[': 446 /* 447 * Embedded style, handled at display time. Leave present and 448 * skip input until ]. 449 */ 450 ch = ']'; 451 goto skip_to; 452 case '#': 453 *(*optr)++ = '#'; 454 break; 455 } 456 457 return; 458 459do_replace: 460 ptrlen = strlen(ptr); 461 if ((size_t) limit < ptrlen) 462 ptrlen = limit; 463 464 if (*optr + ptrlen >= out + outsize - 1) 465 return; 466 while (ptrlen > 0 && *ptr != '\0') { 467 *(*optr)++ = *ptr++; 468 ptrlen--; 469 } 470 471 if (freeptr != NULL) 472 xfree(freeptr); 473 return; 474 475skip_to: 476 *(*optr)++ = '#'; 477 478 (*iptr)--; /* include ch */ 479 while (**iptr != ch && **iptr != '\0') { 480 if (*optr >= out + outsize - 1) 481 break; 482 *(*optr)++ = *(*iptr)++; 483 } 484} 485 486/* Replace special sequences in fmt. */ 487char * 488status_replace(struct client *c, struct session *s, struct winlink *wl, 489 struct window_pane *wp, const char *fmt, time_t t, int jobsflag) 490{ 491 static char out[BUFSIZ]; 492 char in[BUFSIZ], ch, *iptr, *optr; 493 494 strftime(in, sizeof in, fmt, localtime(&t)); 495 in[(sizeof in) - 1] = '\0'; 496 497 iptr = in; 498 optr = out; 499 500 while (*iptr != '\0') { 501 if (optr >= out + (sizeof out) - 1) 502 break; 503 ch = *iptr++; 504 505 if (ch != '#' || *iptr == '\0') { 506 *optr++ = ch; 507 continue; 508 } 509 status_replace1( 510 c, s, wl, wp, &iptr, &optr, out, sizeof out, jobsflag); 511 } 512 *optr = '\0'; 513 514 return (xstrdup(out)); 515} 516 517/* Figure out job name and get its result, starting it off if necessary. */ 518char * 519status_find_job(struct client *c, char **iptr) 520{ 521 struct status_out *so, so_find; 522 char *cmd; 523 int lastesc; 524 size_t len; 525 526 if (**iptr == '\0') 527 return (NULL); 528 if (**iptr == ')') { /* no command given */ 529 (*iptr)++; 530 return (NULL); 531 } 532 533 cmd = xmalloc(strlen(*iptr) + 1); 534 len = 0; 535 536 lastesc = 0; 537 for (; **iptr != '\0'; (*iptr)++) { 538 if (!lastesc && **iptr == ')') 539 break; /* unescaped ) is the end */ 540 if (!lastesc && **iptr == '\\') { 541 lastesc = 1; 542 continue; /* skip \ if not escaped */ 543 } 544 lastesc = 0; 545 cmd[len++] = **iptr; 546 } 547 if (**iptr == '\0') /* no terminating ) */ { 548 xfree(cmd); 549 return (NULL); 550 } 551 (*iptr)++; /* skip final ) */ 552 cmd[len] = '\0'; 553 554 /* First try in the new tree. */ 555 so_find.cmd = cmd; 556 so = RB_FIND(status_out_tree, &c->status_new, &so_find); 557 if (so != NULL && so->out != NULL) { 558 xfree(cmd); 559 return (so->out); 560 } 561 562 /* If not found at all, start the job and add to the tree. */ 563 if (so == NULL) { 564 job_run(cmd, status_job_callback, status_job_free, c); 565 c->references++; 566 567 so = xmalloc(sizeof *so); 568 so->cmd = xstrdup(cmd); 569 so->out = NULL; 570 RB_INSERT(status_out_tree, &c->status_new, so); 571 } 572 573 /* Lookup in the old tree. */ 574 so_find.cmd = cmd; 575 so = RB_FIND(status_out_tree, &c->status_old, &so_find); 576 xfree(cmd); 577 if (so != NULL) 578 return (so->out); 579 return (NULL); 580} 581 582/* Free job tree. */ 583void 584status_free_jobs(struct status_out_tree *sotree) 585{ 586 struct status_out *so, *so_next; 587 588 so_next = RB_MIN(status_out_tree, sotree); 589 while (so_next != NULL) { 590 so = so_next; 591 so_next = RB_NEXT(status_out_tree, sotree, so); 592 593 RB_REMOVE(status_out_tree, sotree, so); 594 if (so->out != NULL) 595 xfree(so->out); 596 xfree(so->cmd); 597 xfree(so); 598 } 599} 600 601/* Update jobs on status interval. */ 602void 603status_update_jobs(struct client *c) 604{ 605 /* Free the old tree. */ 606 status_free_jobs(&c->status_old); 607 608 /* Move the new to old. */ 609 memcpy(&c->status_old, &c->status_new, sizeof c->status_old); 610 RB_INIT(&c->status_new); 611} 612 613/* Free status job. */ 614void 615status_job_free(void *data) 616{ 617 struct client *c = data; 618 619 c->references--; 620} 621 622/* Job has finished: save its result. */ 623void 624status_job_callback(struct job *job) 625{ 626 struct client *c = job->data; 627 struct status_out *so, so_find; 628 char *line, *buf; 629 size_t len; 630 631 if (c->flags & CLIENT_DEAD) 632 return; 633 634 so_find.cmd = job->cmd; 635 so = RB_FIND(status_out_tree, &c->status_new, &so_find); 636 if (so == NULL || so->out != NULL) 637 return; 638 639 buf = NULL; 640 if ((line = evbuffer_readline(job->event->input)) == NULL) { 641 len = EVBUFFER_LENGTH(job->event->input); 642 buf = xmalloc(len + 1); 643 if (len != 0) 644 memcpy(buf, EVBUFFER_DATA(job->event->input), len); 645 buf[len] = '\0'; 646 } else 647 buf = xstrdup(line); 648 649 so->out = buf; 650 server_status_client(c); 651} 652 653/* Return winlink status line entry and adjust gc as necessary. */ 654char * 655status_print( 656 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc) 657{ 658 struct options *oo = &wl->window->options; 659 struct session *s = c->session; 660 const char *fmt; 661 char *text; 662 u_char fg, bg, attr; 663 664 fg = options_get_number(oo, "window-status-fg"); 665 if (fg != 8) 666 colour_set_fg(gc, fg); 667 bg = options_get_number(oo, "window-status-bg"); 668 if (bg != 8) 669 colour_set_bg(gc, bg); 670 attr = options_get_number(oo, "window-status-attr"); 671 if (attr != 0) 672 gc->attr = attr; 673 fmt = options_get_string(oo, "window-status-format"); 674 if (wl == s->curw) { 675 fg = options_get_number(oo, "window-status-current-fg"); 676 if (fg != 8) 677 colour_set_fg(gc, fg); 678 bg = options_get_number(oo, "window-status-current-bg"); 679 if (bg != 8) 680 colour_set_bg(gc, bg); 681 attr = options_get_number(oo, "window-status-current-attr"); 682 if (attr != 0) 683 gc->attr = attr; 684 fmt = options_get_string(oo, "window-status-current-format"); 685 } 686 687 if (wl->flags & WINLINK_ALERTFLAGS) { 688 fg = options_get_number(oo, "window-status-alert-fg"); 689 if (fg != 8) 690 colour_set_fg(gc, fg); 691 bg = options_get_number(oo, "window-status-alert-bg"); 692 if (bg != 8) 693 colour_set_bg(gc, bg); 694 attr = options_get_number(oo, "window-status-alert-attr"); 695 if (attr != 0) 696 gc->attr = attr; 697 } 698 699 text = status_replace(c, NULL, wl, NULL, fmt, t, 1); 700 return (text); 701} 702 703/* Set a status line message. */ 704void printflike2 705status_message_set(struct client *c, const char *fmt, ...) 706{ 707 struct timeval tv; 708 struct session *s = c->session; 709 struct message_entry *msg; 710 va_list ap; 711 int delay; 712 u_int i, limit; 713 714 status_prompt_clear(c); 715 status_message_clear(c); 716 717 va_start(ap, fmt); 718 xvasprintf(&c->message_string, fmt, ap); 719 va_end(ap); 720 721 ARRAY_EXPAND(&c->message_log, 1); 722 msg = &ARRAY_LAST(&c->message_log); 723 msg->msg_time = time(NULL); 724 msg->msg = xstrdup(c->message_string); 725 726 if (s == NULL) 727 limit = 0; 728 else 729 limit = options_get_number(&s->options, "message-limit"); 730 if (ARRAY_LENGTH(&c->message_log) > limit) { 731 limit = ARRAY_LENGTH(&c->message_log) - limit; 732 for (i = 0; i < limit; i++) { 733 msg = &ARRAY_FIRST(&c->message_log); 734 xfree(msg->msg); 735 ARRAY_REMOVE(&c->message_log, 0); 736 } 737 } 738 739 delay = options_get_number(&c->session->options, "display-time"); 740 tv.tv_sec = delay / 1000; 741 tv.tv_usec = (delay % 1000) * 1000L; 742 743 evtimer_del(&c->message_timer); 744 evtimer_set(&c->message_timer, status_message_callback, c); 745 evtimer_add(&c->message_timer, &tv); 746 747 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 748 c->flags |= CLIENT_STATUS; 749} 750 751/* Clear status line message. */ 752void 753status_message_clear(struct client *c) 754{ 755 if (c->message_string == NULL) 756 return; 757 758 xfree(c->message_string); 759 c->message_string = NULL; 760 761 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 762 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 763 764 screen_reinit(&c->status); 765} 766 767/* Clear status line message after timer expires. */ 768/* ARGSUSED */ 769void 770status_message_callback(unused int fd, unused short event, void *data) 771{ 772 struct client *c = data; 773 774 status_message_clear(c); 775} 776 777/* Draw client message on status line of present else on last line. */ 778int 779status_message_redraw(struct client *c) 780{ 781 struct screen_write_ctx ctx; 782 struct session *s = c->session; 783 struct screen old_status; 784 size_t len; 785 struct grid_cell gc; 786 int utf8flag; 787 788 if (c->tty.sx == 0 || c->tty.sy == 0) 789 return (0); 790 memcpy(&old_status, &c->status, sizeof old_status); 791 screen_init(&c->status, c->tty.sx, 1, 0); 792 793 utf8flag = options_get_number(&s->options, "status-utf8"); 794 795 len = screen_write_strlen(utf8flag, "%s", c->message_string); 796 if (len > c->tty.sx) 797 len = c->tty.sx; 798 799 memcpy(&gc, &grid_default_cell, sizeof gc); 800 colour_set_fg(&gc, options_get_number(&s->options, "message-fg")); 801 colour_set_bg(&gc, options_get_number(&s->options, "message-bg")); 802 gc.attr |= options_get_number(&s->options, "message-attr"); 803 804 screen_write_start(&ctx, NULL, &c->status); 805 806 screen_write_cursormove(&ctx, 0, 0); 807 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string); 808 for (; len < c->tty.sx; len++) 809 screen_write_putc(&ctx, &gc, ' '); 810 811 screen_write_stop(&ctx); 812 813 if (grid_compare(c->status.grid, old_status.grid) == 0) { 814 screen_free(&old_status); 815 return (0); 816 } 817 screen_free(&old_status); 818 return (1); 819} 820 821/* Enable status line prompt. */ 822void 823status_prompt_set(struct client *c, const char *msg, const char *input, 824 int (*callbackfn)(void *, const char *), void (*freefn)(void *), 825 void *data, int flags) 826{ 827 int keys; 828 829 status_message_clear(c); 830 status_prompt_clear(c); 831 832 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg, 833 time(NULL), 0); 834 835 if (input == NULL) 836 input = ""; 837 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input, 838 time(NULL), 0); 839 c->prompt_index = strlen(c->prompt_buffer); 840 841 c->prompt_callbackfn = callbackfn; 842 c->prompt_freefn = freefn; 843 c->prompt_data = data; 844 845 c->prompt_hindex = 0; 846 847 c->prompt_flags = flags; 848 849 keys = options_get_number(&c->session->options, "status-keys"); 850 if (keys == MODEKEY_EMACS) 851 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit); 852 else 853 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit); 854 855 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 856 c->flags |= CLIENT_STATUS; 857} 858 859/* Remove status line prompt. */ 860void 861status_prompt_clear(struct client *c) 862{ 863 if (c->prompt_string == NULL) 864 return; 865 866 if (c->prompt_freefn != NULL && c->prompt_data != NULL) 867 c->prompt_freefn(c->prompt_data); 868 869 xfree(c->prompt_string); 870 c->prompt_string = NULL; 871 872 xfree(c->prompt_buffer); 873 c->prompt_buffer = NULL; 874 875 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 876 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 877 878 screen_reinit(&c->status); 879} 880 881/* Update status line prompt with a new prompt string. */ 882void 883status_prompt_update(struct client *c, const char *msg, const char *input) 884{ 885 xfree(c->prompt_string); 886 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg, 887 time(NULL), 0); 888 889 xfree(c->prompt_buffer); 890 if (input == NULL) 891 input = ""; 892 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input, 893 time(NULL), 0); 894 c->prompt_index = strlen(c->prompt_buffer); 895 896 c->prompt_hindex = 0; 897 898 c->flags |= CLIENT_STATUS; 899} 900 901/* Draw client prompt on status line of present else on last line. */ 902int 903status_prompt_redraw(struct client *c) 904{ 905 struct screen_write_ctx ctx; 906 struct session *s = c->session; 907 struct screen old_status; 908 size_t i, size, left, len, off; 909 struct grid_cell gc, *gcp; 910 int utf8flag; 911 912 if (c->tty.sx == 0 || c->tty.sy == 0) 913 return (0); 914 memcpy(&old_status, &c->status, sizeof old_status); 915 screen_init(&c->status, c->tty.sx, 1, 0); 916 917 utf8flag = options_get_number(&s->options, "status-utf8"); 918 919 len = screen_write_strlen(utf8flag, "%s", c->prompt_string); 920 if (len > c->tty.sx) 921 len = c->tty.sx; 922 off = 0; 923 924 memcpy(&gc, &grid_default_cell, sizeof gc); 925 /* Change colours for command mode. */ 926 if (c->prompt_mdata.mode == 1) { 927 colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg")); 928 colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg")); 929 gc.attr |= options_get_number(&s->options, "message-command-attr"); 930 } else { 931 colour_set_fg(&gc, options_get_number(&s->options, "message-fg")); 932 colour_set_bg(&gc, options_get_number(&s->options, "message-bg")); 933 gc.attr |= options_get_number(&s->options, "message-attr"); 934 } 935 936 screen_write_start(&ctx, NULL, &c->status); 937 938 screen_write_cursormove(&ctx, 0, 0); 939 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string); 940 941 left = c->tty.sx - len; 942 if (left != 0) { 943 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer); 944 if (c->prompt_index >= left) { 945 off = c->prompt_index - left + 1; 946 if (c->prompt_index == size) 947 left--; 948 size = left; 949 } 950 screen_write_nputs( 951 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off); 952 953 for (i = len + size; i < c->tty.sx; i++) 954 screen_write_putc(&ctx, &gc, ' '); 955 } 956 957 screen_write_stop(&ctx); 958 959 /* Apply fake cursor. */ 960 off = len + c->prompt_index - off; 961 gcp = grid_view_get_cell(c->status.grid, off, 0); 962 gcp->attr ^= GRID_ATTR_REVERSE; 963 964 if (grid_compare(c->status.grid, old_status.grid) == 0) { 965 screen_free(&old_status); 966 return (0); 967 } 968 screen_free(&old_status); 969 return (1); 970} 971 972/* Handle keys in prompt. */ 973void 974status_prompt_key(struct client *c, int key) 975{ 976 struct paste_buffer *pb; 977 char *s, *first, *last, word[64], swapc; 978 const char *histstr; 979 u_char ch; 980 size_t size, n, off, idx; 981 982 size = strlen(c->prompt_buffer); 983 switch (mode_key_lookup(&c->prompt_mdata, key)) { 984 case MODEKEYEDIT_CURSORLEFT: 985 if (c->prompt_index > 0) { 986 c->prompt_index--; 987 c->flags |= CLIENT_STATUS; 988 } 989 break; 990 case MODEKEYEDIT_SWITCHMODE: 991 c->flags |= CLIENT_STATUS; 992 break; 993 case MODEKEYEDIT_SWITCHMODEAPPEND: 994 c->flags |= CLIENT_STATUS; 995 /* FALLTHROUGH */ 996 case MODEKEYEDIT_CURSORRIGHT: 997 if (c->prompt_index < size) { 998 c->prompt_index++; 999 c->flags |= CLIENT_STATUS; 1000 } 1001 break; 1002 case MODEKEYEDIT_STARTOFLINE: 1003 if (c->prompt_index != 0) { 1004 c->prompt_index = 0; 1005 c->flags |= CLIENT_STATUS; 1006 } 1007 break; 1008 case MODEKEYEDIT_ENDOFLINE: 1009 if (c->prompt_index != size) { 1010 c->prompt_index = size; 1011 c->flags |= CLIENT_STATUS; 1012 } 1013 break; 1014 case MODEKEYEDIT_COMPLETE: 1015 if (*c->prompt_buffer == '\0') 1016 break; 1017 1018 idx = c->prompt_index; 1019 if (idx != 0) 1020 idx--; 1021 1022 /* Find the word we are in. */ 1023 first = c->prompt_buffer + idx; 1024 while (first > c->prompt_buffer && *first != ' ') 1025 first--; 1026 while (*first == ' ') 1027 first++; 1028 last = c->prompt_buffer + idx; 1029 while (*last != '\0' && *last != ' ') 1030 last++; 1031 while (*last == ' ') 1032 last--; 1033 if (*last != '\0') 1034 last++; 1035 if (last <= first || 1036 ((size_t) (last - first)) > (sizeof word) - 1) 1037 break; 1038 memcpy(word, first, last - first); 1039 word[last - first] = '\0'; 1040 1041 /* And try to complete it. */ 1042 if ((s = status_prompt_complete(word)) == NULL) 1043 break; 1044 1045 /* Trim out word. */ 1046 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1047 memmove(first, last, n); 1048 size -= last - first; 1049 1050 /* Insert the new word. */ 1051 size += strlen(s); 1052 off = first - c->prompt_buffer; 1053 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1); 1054 first = c->prompt_buffer + off; 1055 memmove(first + strlen(s), first, n); 1056 memcpy(first, s, strlen(s)); 1057 1058 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1059 xfree(s); 1060 1061 c->flags |= CLIENT_STATUS; 1062 break; 1063 case MODEKEYEDIT_BACKSPACE: 1064 if (c->prompt_index != 0) { 1065 if (c->prompt_index == size) 1066 c->prompt_buffer[--c->prompt_index] = '\0'; 1067 else { 1068 memmove(c->prompt_buffer + c->prompt_index - 1, 1069 c->prompt_buffer + c->prompt_index, 1070 size + 1 - c->prompt_index); 1071 c->prompt_index--; 1072 } 1073 c->flags |= CLIENT_STATUS; 1074 } 1075 break; 1076 case MODEKEYEDIT_DELETE: 1077 if (c->prompt_index != size) { 1078 memmove(c->prompt_buffer + c->prompt_index, 1079 c->prompt_buffer + c->prompt_index + 1, 1080 size + 1 - c->prompt_index); 1081 c->flags |= CLIENT_STATUS; 1082 } 1083 break; 1084 case MODEKEYEDIT_DELETELINE: 1085 *c->prompt_buffer = '\0'; 1086 c->prompt_index = 0; 1087 c->flags |= CLIENT_STATUS; 1088 break; 1089 case MODEKEYEDIT_DELETETOENDOFLINE: 1090 if (c->prompt_index < size) { 1091 c->prompt_buffer[c->prompt_index] = '\0'; 1092 c->flags |= CLIENT_STATUS; 1093 } 1094 break; 1095 case MODEKEYEDIT_HISTORYUP: 1096 histstr = status_prompt_up_history(&c->prompt_hindex); 1097 if (histstr == NULL) 1098 break; 1099 xfree(c->prompt_buffer); 1100 c->prompt_buffer = xstrdup(histstr); 1101 c->prompt_index = strlen(c->prompt_buffer); 1102 c->flags |= CLIENT_STATUS; 1103 break; 1104 case MODEKEYEDIT_HISTORYDOWN: 1105 histstr = status_prompt_down_history(&c->prompt_hindex); 1106 if (histstr == NULL) 1107 break; 1108 xfree(c->prompt_buffer); 1109 c->prompt_buffer = xstrdup(histstr); 1110 c->prompt_index = strlen(c->prompt_buffer); 1111 c->flags |= CLIENT_STATUS; 1112 break; 1113 case MODEKEYEDIT_PASTE: 1114 if ((pb = paste_get_top(&global_buffers)) == NULL) 1115 break; 1116 for (n = 0; n < pb->size; n++) { 1117 ch = (u_char) pb->data[n]; 1118 if (ch < 32 || ch == 127) 1119 break; 1120 } 1121 1122 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1); 1123 if (c->prompt_index == size) { 1124 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); 1125 c->prompt_index += n; 1126 c->prompt_buffer[c->prompt_index] = '\0'; 1127 } else { 1128 memmove(c->prompt_buffer + c->prompt_index + n, 1129 c->prompt_buffer + c->prompt_index, 1130 size + 1 - c->prompt_index); 1131 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); 1132 c->prompt_index += n; 1133 } 1134 1135 c->flags |= CLIENT_STATUS; 1136 break; 1137 case MODEKEYEDIT_TRANSPOSECHARS: 1138 idx = c->prompt_index; 1139 if (idx < size) 1140 idx++; 1141 if (idx >= 2) { 1142 swapc = c->prompt_buffer[idx - 2]; 1143 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1]; 1144 c->prompt_buffer[idx - 1] = swapc; 1145 c->prompt_index = idx; 1146 c->flags |= CLIENT_STATUS; 1147 } 1148 break; 1149 case MODEKEYEDIT_ENTER: 1150 if (*c->prompt_buffer != '\0') 1151 status_prompt_add_history(c->prompt_buffer); 1152 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0) 1153 status_prompt_clear(c); 1154 break; 1155 case MODEKEYEDIT_CANCEL: 1156 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0) 1157 status_prompt_clear(c); 1158 break; 1159 case MODEKEY_OTHER: 1160 if ((key & 0xff00) != 0 || key < 32 || key == 127) 1161 break; 1162 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2); 1163 1164 if (c->prompt_index == size) { 1165 c->prompt_buffer[c->prompt_index++] = key; 1166 c->prompt_buffer[c->prompt_index] = '\0'; 1167 } else { 1168 memmove(c->prompt_buffer + c->prompt_index + 1, 1169 c->prompt_buffer + c->prompt_index, 1170 size + 1 - c->prompt_index); 1171 c->prompt_buffer[c->prompt_index++] = key; 1172 } 1173 1174 if (c->prompt_flags & PROMPT_SINGLE) { 1175 if (c->prompt_callbackfn( 1176 c->prompt_data, c->prompt_buffer) == 0) 1177 status_prompt_clear(c); 1178 } 1179 1180 c->flags |= CLIENT_STATUS; 1181 break; 1182 default: 1183 break; 1184 } 1185} 1186 1187/* Get previous line from the history. */ 1188const char * 1189status_prompt_up_history(u_int *idx) 1190{ 1191 u_int size; 1192 1193 /* 1194 * History runs from 0 to size - 1. 1195 * 1196 * Index is from 0 to size. Zero is empty. 1197 */ 1198 1199 size = ARRAY_LENGTH(&status_prompt_history); 1200 if (size == 0 || *idx == size) 1201 return (NULL); 1202 (*idx)++; 1203 return (ARRAY_ITEM(&status_prompt_history, size - *idx)); 1204} 1205 1206/* Get next line from the history. */ 1207const char * 1208status_prompt_down_history(u_int *idx) 1209{ 1210 u_int size; 1211 1212 size = ARRAY_LENGTH(&status_prompt_history); 1213 if (size == 0 || *idx == 0) 1214 return (""); 1215 (*idx)--; 1216 if (*idx == 0) 1217 return (""); 1218 return (ARRAY_ITEM(&status_prompt_history, size - *idx)); 1219} 1220 1221/* Add line to the history. */ 1222void 1223status_prompt_add_history(const char *line) 1224{ 1225 u_int size; 1226 1227 size = ARRAY_LENGTH(&status_prompt_history); 1228 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0) 1229 return; 1230 1231 if (size == PROMPT_HISTORY) { 1232 xfree(ARRAY_FIRST(&status_prompt_history)); 1233 ARRAY_REMOVE(&status_prompt_history, 0); 1234 } 1235 1236 ARRAY_ADD(&status_prompt_history, xstrdup(line)); 1237} 1238 1239/* Complete word. */ 1240char * 1241status_prompt_complete(const char *s) 1242{ 1243 const struct cmd_entry **cmdent; 1244 const struct options_table_entry *oe; 1245 ARRAY_DECL(, const char *) list; 1246 char *prefix, *s2; 1247 u_int i; 1248 size_t j; 1249 1250 if (*s == '\0') 1251 return (NULL); 1252 1253 /* First, build a list of all the possible matches. */ 1254 ARRAY_INIT(&list); 1255 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1256 if (strncmp((*cmdent)->name, s, strlen(s)) == 0) 1257 ARRAY_ADD(&list, (*cmdent)->name); 1258 } 1259 for (oe = server_options_table; oe->name != NULL; oe++) { 1260 if (strncmp(oe->name, s, strlen(s)) == 0) 1261 ARRAY_ADD(&list, oe->name); 1262 } 1263 for (oe = session_options_table; oe->name != NULL; oe++) { 1264 if (strncmp(oe->name, s, strlen(s)) == 0) 1265 ARRAY_ADD(&list, oe->name); 1266 } 1267 for (oe = window_options_table; oe->name != NULL; oe++) { 1268 if (strncmp(oe->name, s, strlen(s)) == 0) 1269 ARRAY_ADD(&list, oe->name); 1270 } 1271 1272 /* If none, bail now. */ 1273 if (ARRAY_LENGTH(&list) == 0) { 1274 ARRAY_FREE(&list); 1275 return (NULL); 1276 } 1277 1278 /* If an exact match, return it, with a trailing space. */ 1279 if (ARRAY_LENGTH(&list) == 1) { 1280 xasprintf(&s2, "%s ", ARRAY_FIRST(&list)); 1281 ARRAY_FREE(&list); 1282 return (s2); 1283 } 1284 1285 /* Now loop through the list and find the longest common prefix. */ 1286 prefix = xstrdup(ARRAY_FIRST(&list)); 1287 for (i = 1; i < ARRAY_LENGTH(&list); i++) { 1288 s = ARRAY_ITEM(&list, i); 1289 1290 j = strlen(s); 1291 if (j > strlen(prefix)) 1292 j = strlen(prefix); 1293 for (; j > 0; j--) { 1294 if (prefix[j - 1] != s[j - 1]) 1295 prefix[j - 1] = '\0'; 1296 } 1297 } 1298 1299 ARRAY_FREE(&list); 1300 return (prefix); 1301} 1302