1 // SPDX-License-Identifier: GPL-2.0 2 #include "../util.h" 3 #include "../string2.h" 4 #include "../config.h" 5 #include "../../perf.h" 6 #include "libslang.h" 7 #include "ui.h" 8 #include "util.h" 9 #include <linux/compiler.h> 10 #include <linux/list.h> 11 #include <linux/rbtree.h> 12 #include <linux/string.h> 13 #include <stdlib.h> 14 #include <sys/ttydefaults.h> 15 #include "browser.h" 16 #include "helpline.h" 17 #include "keysyms.h" 18 #include "../color.h" 19 #include "sane_ctype.h" 20 21 static int ui_browser__percent_color(struct ui_browser *browser, 22 double percent, bool current) 23 { 24 if (current && (!browser->use_navkeypressed || browser->navkeypressed)) 25 return HE_COLORSET_SELECTED; 26 if (percent >= MIN_RED) 27 return HE_COLORSET_TOP; 28 if (percent >= MIN_GREEN) 29 return HE_COLORSET_MEDIUM; 30 return HE_COLORSET_NORMAL; 31 } 32 33 int ui_browser__set_color(struct ui_browser *browser, int color) 34 { 35 int ret = browser->current_color; 36 browser->current_color = color; 37 SLsmg_set_color(color); 38 return ret; 39 } 40 41 void ui_browser__set_percent_color(struct ui_browser *browser, 42 double percent, bool current) 43 { 44 int color = ui_browser__percent_color(browser, percent, current); 45 ui_browser__set_color(browser, color); 46 } 47 48 void ui_browser__gotorc_title(struct ui_browser *browser, int y, int x) 49 { 50 SLsmg_gotorc(browser->y + y, browser->x + x); 51 } 52 53 void ui_browser__gotorc(struct ui_browser *browser, int y, int x) 54 { 55 SLsmg_gotorc(browser->y + y + browser->extra_title_lines, browser->x + x); 56 } 57 58 void ui_browser__write_nstring(struct ui_browser *browser __maybe_unused, const char *msg, 59 unsigned int width) 60 { 61 slsmg_write_nstring(msg, width); 62 } 63 64 void ui_browser__vprintf(struct ui_browser *browser __maybe_unused, const char *fmt, va_list args) 65 { 66 slsmg_vprintf(fmt, args); 67 } 68 69 void ui_browser__printf(struct ui_browser *browser __maybe_unused, const char *fmt, ...) 70 { 71 va_list args; 72 73 va_start(args, fmt); 74 ui_browser__vprintf(browser, fmt, args); 75 va_end(args); 76 } 77 78 static struct list_head * 79 ui_browser__list_head_filter_entries(struct ui_browser *browser, 80 struct list_head *pos) 81 { 82 do { 83 if (!browser->filter || !browser->filter(browser, pos)) 84 return pos; 85 pos = pos->next; 86 } while (pos != browser->entries); 87 88 return NULL; 89 } 90 91 static struct list_head * 92 ui_browser__list_head_filter_prev_entries(struct ui_browser *browser, 93 struct list_head *pos) 94 { 95 do { 96 if (!browser->filter || !browser->filter(browser, pos)) 97 return pos; 98 pos = pos->prev; 99 } while (pos != browser->entries); 100 101 return NULL; 102 } 103 104 void ui_browser__list_head_seek(struct ui_browser *browser, off_t offset, int whence) 105 { 106 struct list_head *head = browser->entries; 107 struct list_head *pos; 108 109 if (browser->nr_entries == 0) 110 return; 111 112 switch (whence) { 113 case SEEK_SET: 114 pos = ui_browser__list_head_filter_entries(browser, head->next); 115 break; 116 case SEEK_CUR: 117 pos = browser->top; 118 break; 119 case SEEK_END: 120 pos = ui_browser__list_head_filter_prev_entries(browser, head->prev); 121 break; 122 default: 123 return; 124 } 125 126 assert(pos != NULL); 127 128 if (offset > 0) { 129 while (offset-- != 0) 130 pos = ui_browser__list_head_filter_entries(browser, pos->next); 131 } else { 132 while (offset++ != 0) 133 pos = ui_browser__list_head_filter_prev_entries(browser, pos->prev); 134 } 135 136 browser->top = pos; 137 } 138 139 void ui_browser__rb_tree_seek(struct ui_browser *browser, off_t offset, int whence) 140 { 141 struct rb_root *root = browser->entries; 142 struct rb_node *nd; 143 144 switch (whence) { 145 case SEEK_SET: 146 nd = rb_first(root); 147 break; 148 case SEEK_CUR: 149 nd = browser->top; 150 break; 151 case SEEK_END: 152 nd = rb_last(root); 153 break; 154 default: 155 return; 156 } 157 158 if (offset > 0) { 159 while (offset-- != 0) 160 nd = rb_next(nd); 161 } else { 162 while (offset++ != 0) 163 nd = rb_prev(nd); 164 } 165 166 browser->top = nd; 167 } 168 169 unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser) 170 { 171 struct rb_node *nd; 172 int row = 0; 173 174 if (browser->top == NULL) 175 browser->top = rb_first(browser->entries); 176 177 nd = browser->top; 178 179 while (nd != NULL) { 180 ui_browser__gotorc(browser, row, 0); 181 browser->write(browser, nd, row); 182 if (++row == browser->rows) 183 break; 184 nd = rb_next(nd); 185 } 186 187 return row; 188 } 189 190 bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row) 191 { 192 return browser->top_idx + row == browser->index; 193 } 194 195 void ui_browser__refresh_dimensions(struct ui_browser *browser) 196 { 197 browser->width = SLtt_Screen_Cols - 1; 198 browser->height = browser->rows = SLtt_Screen_Rows - 2; 199 browser->rows -= browser->extra_title_lines; 200 browser->y = 1; 201 browser->x = 0; 202 } 203 204 void ui_browser__handle_resize(struct ui_browser *browser) 205 { 206 ui__refresh_dimensions(false); 207 ui_browser__show(browser, browser->title, ui_helpline__current); 208 ui_browser__refresh(browser); 209 } 210 211 int ui_browser__warning(struct ui_browser *browser, int timeout, 212 const char *format, ...) 213 { 214 va_list args; 215 char *text; 216 int key = 0, err; 217 218 va_start(args, format); 219 err = vasprintf(&text, format, args); 220 va_end(args); 221 222 if (err < 0) { 223 va_start(args, format); 224 ui_helpline__vpush(format, args); 225 va_end(args); 226 } else { 227 while ((key = ui__question_window("Warning!", text, 228 "Press any key...", 229 timeout)) == K_RESIZE) 230 ui_browser__handle_resize(browser); 231 free(text); 232 } 233 234 return key; 235 } 236 237 int ui_browser__help_window(struct ui_browser *browser, const char *text) 238 { 239 int key; 240 241 while ((key = ui__help_window(text)) == K_RESIZE) 242 ui_browser__handle_resize(browser); 243 244 return key; 245 } 246 247 bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text) 248 { 249 int key; 250 251 while ((key = ui__dialog_yesno(text)) == K_RESIZE) 252 ui_browser__handle_resize(browser); 253 254 return key == K_ENTER || toupper(key) == 'Y'; 255 } 256 257 void ui_browser__reset_index(struct ui_browser *browser) 258 { 259 browser->index = browser->top_idx = 0; 260 browser->seek(browser, 0, SEEK_SET); 261 } 262 263 void __ui_browser__show_title(struct ui_browser *browser, const char *title) 264 { 265 SLsmg_gotorc(0, 0); 266 ui_browser__set_color(browser, HE_COLORSET_ROOT); 267 ui_browser__write_nstring(browser, title, browser->width + 1); 268 } 269 270 void ui_browser__show_title(struct ui_browser *browser, const char *title) 271 { 272 pthread_mutex_lock(&ui__lock); 273 __ui_browser__show_title(browser, title); 274 pthread_mutex_unlock(&ui__lock); 275 } 276 277 int ui_browser__show(struct ui_browser *browser, const char *title, 278 const char *helpline, ...) 279 { 280 int err; 281 va_list ap; 282 283 if (browser->refresh_dimensions == NULL) 284 browser->refresh_dimensions = ui_browser__refresh_dimensions; 285 286 browser->refresh_dimensions(browser); 287 288 pthread_mutex_lock(&ui__lock); 289 __ui_browser__show_title(browser, title); 290 291 browser->title = title; 292 zfree(&browser->helpline); 293 294 va_start(ap, helpline); 295 err = vasprintf(&browser->helpline, helpline, ap); 296 va_end(ap); 297 if (err > 0) 298 ui_helpline__push(browser->helpline); 299 pthread_mutex_unlock(&ui__lock); 300 return err ? 0 : -1; 301 } 302 303 void ui_browser__hide(struct ui_browser *browser) 304 { 305 pthread_mutex_lock(&ui__lock); 306 ui_helpline__pop(); 307 zfree(&browser->helpline); 308 pthread_mutex_unlock(&ui__lock); 309 } 310 311 static void ui_browser__scrollbar_set(struct ui_browser *browser) 312 { 313 int height = browser->height, h = 0, pct = 0, 314 col = browser->width, 315 row = 0; 316 317 if (browser->nr_entries > 1) { 318 pct = ((browser->index * (browser->height - 1)) / 319 (browser->nr_entries - 1)); 320 } 321 322 SLsmg_set_char_set(1); 323 324 while (h < height) { 325 ui_browser__gotorc(browser, row++, col); 326 SLsmg_write_char(h == pct ? SLSMG_DIAMOND_CHAR : SLSMG_CKBRD_CHAR); 327 ++h; 328 } 329 330 SLsmg_set_char_set(0); 331 } 332 333 static int __ui_browser__refresh(struct ui_browser *browser) 334 { 335 int row; 336 int width = browser->width; 337 338 row = browser->refresh(browser); 339 ui_browser__set_color(browser, HE_COLORSET_NORMAL); 340 341 if (!browser->use_navkeypressed || browser->navkeypressed) 342 ui_browser__scrollbar_set(browser); 343 else 344 width += 1; 345 346 SLsmg_fill_region(browser->y + row + browser->extra_title_lines, browser->x, 347 browser->rows - row, width, ' '); 348 349 return 0; 350 } 351 352 int ui_browser__refresh(struct ui_browser *browser) 353 { 354 pthread_mutex_lock(&ui__lock); 355 __ui_browser__refresh(browser); 356 pthread_mutex_unlock(&ui__lock); 357 358 return 0; 359 } 360 361 /* 362 * Here we're updating nr_entries _after_ we started browsing, i.e. we have to 363 * forget about any reference to any entry in the underlying data structure, 364 * that is why we do a SEEK_SET. Think about 'perf top' in the hists browser 365 * after an output_resort and hist decay. 366 */ 367 void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries) 368 { 369 off_t offset = nr_entries - browser->nr_entries; 370 371 browser->nr_entries = nr_entries; 372 373 if (offset < 0) { 374 if (browser->top_idx < (u64)-offset) 375 offset = -browser->top_idx; 376 377 browser->index += offset; 378 browser->top_idx += offset; 379 } 380 381 browser->top = NULL; 382 browser->seek(browser, browser->top_idx, SEEK_SET); 383 } 384 385 int ui_browser__run(struct ui_browser *browser, int delay_secs) 386 { 387 int err, key; 388 389 while (1) { 390 off_t offset; 391 392 pthread_mutex_lock(&ui__lock); 393 err = __ui_browser__refresh(browser); 394 SLsmg_refresh(); 395 pthread_mutex_unlock(&ui__lock); 396 if (err < 0) 397 break; 398 399 key = ui__getch(delay_secs); 400 401 if (key == K_RESIZE) { 402 ui__refresh_dimensions(false); 403 browser->refresh_dimensions(browser); 404 __ui_browser__show_title(browser, browser->title); 405 ui_helpline__puts(browser->helpline); 406 continue; 407 } 408 409 if (browser->use_navkeypressed && !browser->navkeypressed) { 410 if (key == K_DOWN || key == K_UP || 411 (browser->columns && (key == K_LEFT || key == K_RIGHT)) || 412 key == K_PGDN || key == K_PGUP || 413 key == K_HOME || key == K_END || 414 key == ' ') { 415 browser->navkeypressed = true; 416 continue; 417 } else 418 return key; 419 } 420 421 switch (key) { 422 case K_DOWN: 423 if (browser->index == browser->nr_entries - 1) 424 break; 425 ++browser->index; 426 if (browser->index == browser->top_idx + browser->rows) { 427 ++browser->top_idx; 428 browser->seek(browser, +1, SEEK_CUR); 429 } 430 break; 431 case K_UP: 432 if (browser->index == 0) 433 break; 434 --browser->index; 435 if (browser->index < browser->top_idx) { 436 --browser->top_idx; 437 browser->seek(browser, -1, SEEK_CUR); 438 } 439 break; 440 case K_RIGHT: 441 if (!browser->columns) 442 goto out; 443 if (browser->horiz_scroll < browser->columns - 1) 444 ++browser->horiz_scroll; 445 break; 446 case K_LEFT: 447 if (!browser->columns) 448 goto out; 449 if (browser->horiz_scroll != 0) 450 --browser->horiz_scroll; 451 break; 452 case K_PGDN: 453 case ' ': 454 if (browser->top_idx + browser->rows > browser->nr_entries - 1) 455 break; 456 457 offset = browser->rows; 458 if (browser->index + offset > browser->nr_entries - 1) 459 offset = browser->nr_entries - 1 - browser->index; 460 browser->index += offset; 461 browser->top_idx += offset; 462 browser->seek(browser, +offset, SEEK_CUR); 463 break; 464 case K_PGUP: 465 if (browser->top_idx == 0) 466 break; 467 468 if (browser->top_idx < browser->rows) 469 offset = browser->top_idx; 470 else 471 offset = browser->rows; 472 473 browser->index -= offset; 474 browser->top_idx -= offset; 475 browser->seek(browser, -offset, SEEK_CUR); 476 break; 477 case K_HOME: 478 ui_browser__reset_index(browser); 479 break; 480 case K_END: 481 offset = browser->rows - 1; 482 if (offset >= browser->nr_entries) 483 offset = browser->nr_entries - 1; 484 485 browser->index = browser->nr_entries - 1; 486 browser->top_idx = browser->index - offset; 487 browser->seek(browser, -offset, SEEK_END); 488 break; 489 default: 490 out: 491 return key; 492 } 493 } 494 return -1; 495 } 496 497 unsigned int ui_browser__list_head_refresh(struct ui_browser *browser) 498 { 499 struct list_head *pos; 500 struct list_head *head = browser->entries; 501 int row = 0; 502 503 if (browser->top == NULL || browser->top == browser->entries) 504 browser->top = ui_browser__list_head_filter_entries(browser, head->next); 505 506 pos = browser->top; 507 508 list_for_each_from(pos, head) { 509 if (!browser->filter || !browser->filter(browser, pos)) { 510 ui_browser__gotorc(browser, row, 0); 511 browser->write(browser, pos, row); 512 if (++row == browser->rows) 513 break; 514 } 515 } 516 517 return row; 518 } 519 520 static struct ui_browser_colorset { 521 const char *name, *fg, *bg; 522 int colorset; 523 } ui_browser__colorsets[] = { 524 { 525 .colorset = HE_COLORSET_TOP, 526 .name = "top", 527 .fg = "red", 528 .bg = "default", 529 }, 530 { 531 .colorset = HE_COLORSET_MEDIUM, 532 .name = "medium", 533 .fg = "green", 534 .bg = "default", 535 }, 536 { 537 .colorset = HE_COLORSET_NORMAL, 538 .name = "normal", 539 .fg = "default", 540 .bg = "default", 541 }, 542 { 543 .colorset = HE_COLORSET_SELECTED, 544 .name = "selected", 545 .fg = "black", 546 .bg = "yellow", 547 }, 548 { 549 .colorset = HE_COLORSET_JUMP_ARROWS, 550 .name = "jump_arrows", 551 .fg = "blue", 552 .bg = "default", 553 }, 554 { 555 .colorset = HE_COLORSET_ADDR, 556 .name = "addr", 557 .fg = "magenta", 558 .bg = "default", 559 }, 560 { 561 .colorset = HE_COLORSET_ROOT, 562 .name = "root", 563 .fg = "white", 564 .bg = "blue", 565 }, 566 { 567 .name = NULL, 568 } 569 }; 570 571 572 static int ui_browser__color_config(const char *var, const char *value, 573 void *data __maybe_unused) 574 { 575 char *fg = NULL, *bg; 576 int i; 577 578 /* same dir for all commands */ 579 if (!strstarts(var, "colors.") != 0) 580 return 0; 581 582 for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) { 583 const char *name = var + 7; 584 585 if (strcmp(ui_browser__colorsets[i].name, name) != 0) 586 continue; 587 588 fg = strdup(value); 589 if (fg == NULL) 590 break; 591 592 bg = strchr(fg, ','); 593 if (bg == NULL) 594 break; 595 596 *bg = '\0'; 597 bg = ltrim(++bg); 598 ui_browser__colorsets[i].bg = bg; 599 ui_browser__colorsets[i].fg = fg; 600 return 0; 601 } 602 603 free(fg); 604 return -1; 605 } 606 607 void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence) 608 { 609 switch (whence) { 610 case SEEK_SET: 611 browser->top = browser->entries; 612 break; 613 case SEEK_CUR: 614 browser->top = browser->top + browser->top_idx + offset; 615 break; 616 case SEEK_END: 617 browser->top = browser->top + browser->nr_entries - 1 + offset; 618 break; 619 default: 620 return; 621 } 622 } 623 624 unsigned int ui_browser__argv_refresh(struct ui_browser *browser) 625 { 626 unsigned int row = 0, idx = browser->top_idx; 627 char **pos; 628 629 if (browser->top == NULL) 630 browser->top = browser->entries; 631 632 pos = (char **)browser->top; 633 while (idx < browser->nr_entries) { 634 if (!browser->filter || !browser->filter(browser, *pos)) { 635 ui_browser__gotorc(browser, row, 0); 636 browser->write(browser, pos, row); 637 if (++row == browser->rows) 638 break; 639 } 640 641 ++idx; 642 ++pos; 643 } 644 645 return row; 646 } 647 648 void __ui_browser__vline(struct ui_browser *browser, unsigned int column, 649 u16 start, u16 end) 650 { 651 SLsmg_set_char_set(1); 652 ui_browser__gotorc(browser, start, column); 653 SLsmg_draw_vline(end - start + 1); 654 SLsmg_set_char_set(0); 655 } 656 657 void ui_browser__write_graph(struct ui_browser *browser __maybe_unused, 658 int graph) 659 { 660 SLsmg_set_char_set(1); 661 SLsmg_write_char(graph); 662 SLsmg_set_char_set(0); 663 } 664 665 static void __ui_browser__line_arrow_up(struct ui_browser *browser, 666 unsigned int column, 667 u64 start, u64 end) 668 { 669 unsigned int row, end_row; 670 671 SLsmg_set_char_set(1); 672 673 if (start < browser->top_idx + browser->rows) { 674 row = start - browser->top_idx; 675 ui_browser__gotorc(browser, row, column); 676 SLsmg_write_char(SLSMG_LLCORN_CHAR); 677 ui_browser__gotorc(browser, row, column + 1); 678 SLsmg_draw_hline(2); 679 680 if (row-- == 0) 681 goto out; 682 } else 683 row = browser->rows - 1; 684 685 if (end > browser->top_idx) 686 end_row = end - browser->top_idx; 687 else 688 end_row = 0; 689 690 ui_browser__gotorc(browser, end_row, column); 691 SLsmg_draw_vline(row - end_row + 1); 692 693 ui_browser__gotorc(browser, end_row, column); 694 if (end >= browser->top_idx) { 695 SLsmg_write_char(SLSMG_ULCORN_CHAR); 696 ui_browser__gotorc(browser, end_row, column + 1); 697 SLsmg_write_char(SLSMG_HLINE_CHAR); 698 ui_browser__gotorc(browser, end_row, column + 2); 699 SLsmg_write_char(SLSMG_RARROW_CHAR); 700 } 701 out: 702 SLsmg_set_char_set(0); 703 } 704 705 static void __ui_browser__line_arrow_down(struct ui_browser *browser, 706 unsigned int column, 707 u64 start, u64 end) 708 { 709 unsigned int row, end_row; 710 711 SLsmg_set_char_set(1); 712 713 if (start >= browser->top_idx) { 714 row = start - browser->top_idx; 715 ui_browser__gotorc(browser, row, column); 716 SLsmg_write_char(SLSMG_ULCORN_CHAR); 717 ui_browser__gotorc(browser, row, column + 1); 718 SLsmg_draw_hline(2); 719 720 if (++row == 0) 721 goto out; 722 } else 723 row = 0; 724 725 if (end >= browser->top_idx + browser->rows) 726 end_row = browser->rows - 1; 727 else 728 end_row = end - browser->top_idx; 729 730 ui_browser__gotorc(browser, row, column); 731 SLsmg_draw_vline(end_row - row + 1); 732 733 ui_browser__gotorc(browser, end_row, column); 734 if (end < browser->top_idx + browser->rows) { 735 SLsmg_write_char(SLSMG_LLCORN_CHAR); 736 ui_browser__gotorc(browser, end_row, column + 1); 737 SLsmg_write_char(SLSMG_HLINE_CHAR); 738 ui_browser__gotorc(browser, end_row, column + 2); 739 SLsmg_write_char(SLSMG_RARROW_CHAR); 740 } 741 out: 742 SLsmg_set_char_set(0); 743 } 744 745 void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column, 746 u64 start, u64 end) 747 { 748 if (start > end) 749 __ui_browser__line_arrow_up(browser, column, start, end); 750 else 751 __ui_browser__line_arrow_down(browser, column, start, end); 752 } 753 754 void ui_browser__mark_fused(struct ui_browser *browser, unsigned int column, 755 unsigned int row, bool arrow_down) 756 { 757 unsigned int end_row; 758 759 if (row >= browser->top_idx) 760 end_row = row - browser->top_idx; 761 else 762 return; 763 764 SLsmg_set_char_set(1); 765 766 if (arrow_down) { 767 ui_browser__gotorc(browser, end_row, column - 1); 768 SLsmg_write_char(SLSMG_ULCORN_CHAR); 769 ui_browser__gotorc(browser, end_row, column); 770 SLsmg_draw_hline(2); 771 ui_browser__gotorc(browser, end_row + 1, column - 1); 772 SLsmg_write_char(SLSMG_LTEE_CHAR); 773 } else { 774 ui_browser__gotorc(browser, end_row, column - 1); 775 SLsmg_write_char(SLSMG_LTEE_CHAR); 776 ui_browser__gotorc(browser, end_row, column); 777 SLsmg_draw_hline(2); 778 } 779 780 SLsmg_set_char_set(0); 781 } 782 783 void ui_browser__init(void) 784 { 785 int i = 0; 786 787 perf_config(ui_browser__color_config, NULL); 788 789 while (ui_browser__colorsets[i].name) { 790 struct ui_browser_colorset *c = &ui_browser__colorsets[i++]; 791 sltt_set_color(c->colorset, c->name, c->fg, c->bg); 792 } 793 } 794