1 // SPDX-License-Identifier: GPL-2.0 2 #include "../util/util.h" 3 #include "../util/string2.h" 4 #include "../util/config.h" 5 #include "libslang.h" 6 #include "ui.h" 7 #include "util.h" 8 #include <linux/compiler.h> 9 #include <linux/list.h> 10 #include <linux/rbtree.h> 11 #include <linux/string.h> 12 #include <stdlib.h> 13 #include <sys/ttydefaults.h> 14 #include "browser.h" 15 #include "helpline.h" 16 #include "keysyms.h" 17 #include "../util/color.h" 18 #include <linux/ctype.h> 19 #include <linux/zalloc.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 if (browser->nr_entries == 0 && browser->no_samples_msg) 350 __ui__info_window(NULL, browser->no_samples_msg, NULL); 351 return 0; 352 } 353 354 int ui_browser__refresh(struct ui_browser *browser) 355 { 356 pthread_mutex_lock(&ui__lock); 357 __ui_browser__refresh(browser); 358 pthread_mutex_unlock(&ui__lock); 359 360 return 0; 361 } 362 363 /* 364 * Here we're updating nr_entries _after_ we started browsing, i.e. we have to 365 * forget about any reference to any entry in the underlying data structure, 366 * that is why we do a SEEK_SET. Think about 'perf top' in the hists browser 367 * after an output_resort and hist decay. 368 */ 369 void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries) 370 { 371 off_t offset = nr_entries - browser->nr_entries; 372 373 browser->nr_entries = nr_entries; 374 375 if (offset < 0) { 376 if (browser->top_idx < (u64)-offset) 377 offset = -browser->top_idx; 378 379 browser->index += offset; 380 browser->top_idx += offset; 381 } 382 383 browser->top = NULL; 384 browser->seek(browser, browser->top_idx, SEEK_SET); 385 } 386 387 int ui_browser__run(struct ui_browser *browser, int delay_secs) 388 { 389 int err, key; 390 391 while (1) { 392 off_t offset; 393 394 pthread_mutex_lock(&ui__lock); 395 err = __ui_browser__refresh(browser); 396 SLsmg_refresh(); 397 pthread_mutex_unlock(&ui__lock); 398 if (err < 0) 399 break; 400 401 key = ui__getch(delay_secs); 402 403 if (key == K_RESIZE) { 404 ui__refresh_dimensions(false); 405 browser->refresh_dimensions(browser); 406 __ui_browser__show_title(browser, browser->title); 407 ui_helpline__puts(browser->helpline); 408 continue; 409 } 410 411 if (browser->use_navkeypressed && !browser->navkeypressed) { 412 if (key == K_DOWN || key == K_UP || 413 (browser->columns && (key == K_LEFT || key == K_RIGHT)) || 414 key == K_PGDN || key == K_PGUP || 415 key == K_HOME || key == K_END || 416 key == ' ') { 417 browser->navkeypressed = true; 418 continue; 419 } else 420 return key; 421 } 422 423 switch (key) { 424 case K_DOWN: 425 if (browser->index == browser->nr_entries - 1) 426 break; 427 ++browser->index; 428 if (browser->index == browser->top_idx + browser->rows) { 429 ++browser->top_idx; 430 browser->seek(browser, +1, SEEK_CUR); 431 } 432 break; 433 case K_UP: 434 if (browser->index == 0) 435 break; 436 --browser->index; 437 if (browser->index < browser->top_idx) { 438 --browser->top_idx; 439 browser->seek(browser, -1, SEEK_CUR); 440 } 441 break; 442 case K_RIGHT: 443 if (!browser->columns) 444 goto out; 445 if (browser->horiz_scroll < browser->columns - 1) 446 ++browser->horiz_scroll; 447 break; 448 case K_LEFT: 449 if (!browser->columns) 450 goto out; 451 if (browser->horiz_scroll != 0) 452 --browser->horiz_scroll; 453 break; 454 case K_PGDN: 455 case ' ': 456 if (browser->top_idx + browser->rows > browser->nr_entries - 1) 457 break; 458 459 offset = browser->rows; 460 if (browser->index + offset > browser->nr_entries - 1) 461 offset = browser->nr_entries - 1 - browser->index; 462 browser->index += offset; 463 browser->top_idx += offset; 464 browser->seek(browser, +offset, SEEK_CUR); 465 break; 466 case K_PGUP: 467 if (browser->top_idx == 0) 468 break; 469 470 if (browser->top_idx < browser->rows) 471 offset = browser->top_idx; 472 else 473 offset = browser->rows; 474 475 browser->index -= offset; 476 browser->top_idx -= offset; 477 browser->seek(browser, -offset, SEEK_CUR); 478 break; 479 case K_HOME: 480 ui_browser__reset_index(browser); 481 break; 482 case K_END: 483 offset = browser->rows - 1; 484 if (offset >= browser->nr_entries) 485 offset = browser->nr_entries - 1; 486 487 browser->index = browser->nr_entries - 1; 488 browser->top_idx = browser->index - offset; 489 browser->seek(browser, -offset, SEEK_END); 490 break; 491 default: 492 out: 493 return key; 494 } 495 } 496 return -1; 497 } 498 499 unsigned int ui_browser__list_head_refresh(struct ui_browser *browser) 500 { 501 struct list_head *pos; 502 struct list_head *head = browser->entries; 503 int row = 0; 504 505 if (browser->top == NULL || browser->top == browser->entries) 506 browser->top = ui_browser__list_head_filter_entries(browser, head->next); 507 508 pos = browser->top; 509 510 list_for_each_from(pos, head) { 511 if (!browser->filter || !browser->filter(browser, pos)) { 512 ui_browser__gotorc(browser, row, 0); 513 browser->write(browser, pos, row); 514 if (++row == browser->rows) 515 break; 516 } 517 } 518 519 return row; 520 } 521 522 static struct ui_browser_colorset { 523 const char *name, *fg, *bg; 524 int colorset; 525 } ui_browser__colorsets[] = { 526 { 527 .colorset = HE_COLORSET_TOP, 528 .name = "top", 529 .fg = "red", 530 .bg = "default", 531 }, 532 { 533 .colorset = HE_COLORSET_MEDIUM, 534 .name = "medium", 535 .fg = "green", 536 .bg = "default", 537 }, 538 { 539 .colorset = HE_COLORSET_NORMAL, 540 .name = "normal", 541 .fg = "default", 542 .bg = "default", 543 }, 544 { 545 .colorset = HE_COLORSET_SELECTED, 546 .name = "selected", 547 .fg = "black", 548 .bg = "yellow", 549 }, 550 { 551 .colorset = HE_COLORSET_JUMP_ARROWS, 552 .name = "jump_arrows", 553 .fg = "blue", 554 .bg = "default", 555 }, 556 { 557 .colorset = HE_COLORSET_ADDR, 558 .name = "addr", 559 .fg = "magenta", 560 .bg = "default", 561 }, 562 { 563 .colorset = HE_COLORSET_ROOT, 564 .name = "root", 565 .fg = "white", 566 .bg = "blue", 567 }, 568 { 569 .name = NULL, 570 } 571 }; 572 573 574 static int ui_browser__color_config(const char *var, const char *value, 575 void *data __maybe_unused) 576 { 577 char *fg = NULL, *bg; 578 int i; 579 580 /* same dir for all commands */ 581 if (!strstarts(var, "colors.") != 0) 582 return 0; 583 584 for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) { 585 const char *name = var + 7; 586 587 if (strcmp(ui_browser__colorsets[i].name, name) != 0) 588 continue; 589 590 fg = strdup(value); 591 if (fg == NULL) 592 break; 593 594 bg = strchr(fg, ','); 595 if (bg == NULL) 596 break; 597 598 *bg = '\0'; 599 bg = skip_spaces(bg + 1); 600 ui_browser__colorsets[i].bg = bg; 601 ui_browser__colorsets[i].fg = fg; 602 return 0; 603 } 604 605 free(fg); 606 return -1; 607 } 608 609 void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence) 610 { 611 switch (whence) { 612 case SEEK_SET: 613 browser->top = browser->entries; 614 break; 615 case SEEK_CUR: 616 browser->top = (char **)browser->top + offset; 617 break; 618 case SEEK_END: 619 browser->top = (char **)browser->entries + browser->nr_entries - 1 + offset; 620 break; 621 default: 622 return; 623 } 624 assert((char **)browser->top < (char **)browser->entries + browser->nr_entries); 625 assert((char **)browser->top >= (char **)browser->entries); 626 } 627 628 unsigned int ui_browser__argv_refresh(struct ui_browser *browser) 629 { 630 unsigned int row = 0, idx = browser->top_idx; 631 char **pos; 632 633 if (browser->top == NULL) 634 browser->top = browser->entries; 635 636 pos = (char **)browser->top; 637 while (idx < browser->nr_entries && 638 row < (unsigned)SLtt_Screen_Rows - 1) { 639 assert(pos < (char **)browser->entries + browser->nr_entries); 640 if (!browser->filter || !browser->filter(browser, *pos)) { 641 ui_browser__gotorc(browser, row, 0); 642 browser->write(browser, pos, row); 643 if (++row == browser->rows) 644 break; 645 } 646 647 ++idx; 648 ++pos; 649 } 650 651 return row; 652 } 653 654 void __ui_browser__vline(struct ui_browser *browser, unsigned int column, 655 u16 start, u16 end) 656 { 657 SLsmg_set_char_set(1); 658 ui_browser__gotorc(browser, start, column); 659 SLsmg_draw_vline(end - start + 1); 660 SLsmg_set_char_set(0); 661 } 662 663 void ui_browser__write_graph(struct ui_browser *browser __maybe_unused, 664 int graph) 665 { 666 SLsmg_set_char_set(1); 667 SLsmg_write_char(graph); 668 SLsmg_set_char_set(0); 669 } 670 671 static void __ui_browser__line_arrow_up(struct ui_browser *browser, 672 unsigned int column, 673 u64 start, u64 end) 674 { 675 unsigned int row, end_row; 676 677 SLsmg_set_char_set(1); 678 679 if (start < browser->top_idx + browser->rows) { 680 row = start - browser->top_idx; 681 ui_browser__gotorc(browser, row, column); 682 SLsmg_write_char(SLSMG_LLCORN_CHAR); 683 ui_browser__gotorc(browser, row, column + 1); 684 SLsmg_draw_hline(2); 685 686 if (row-- == 0) 687 goto out; 688 } else 689 row = browser->rows - 1; 690 691 if (end > browser->top_idx) 692 end_row = end - browser->top_idx; 693 else 694 end_row = 0; 695 696 ui_browser__gotorc(browser, end_row, column); 697 SLsmg_draw_vline(row - end_row + 1); 698 699 ui_browser__gotorc(browser, end_row, column); 700 if (end >= browser->top_idx) { 701 SLsmg_write_char(SLSMG_ULCORN_CHAR); 702 ui_browser__gotorc(browser, end_row, column + 1); 703 SLsmg_write_char(SLSMG_HLINE_CHAR); 704 ui_browser__gotorc(browser, end_row, column + 2); 705 SLsmg_write_char(SLSMG_RARROW_CHAR); 706 } 707 out: 708 SLsmg_set_char_set(0); 709 } 710 711 static void __ui_browser__line_arrow_down(struct ui_browser *browser, 712 unsigned int column, 713 u64 start, u64 end) 714 { 715 unsigned int row, end_row; 716 717 SLsmg_set_char_set(1); 718 719 if (start >= browser->top_idx) { 720 row = start - browser->top_idx; 721 ui_browser__gotorc(browser, row, column); 722 SLsmg_write_char(SLSMG_ULCORN_CHAR); 723 ui_browser__gotorc(browser, row, column + 1); 724 SLsmg_draw_hline(2); 725 726 if (++row == 0) 727 goto out; 728 } else 729 row = 0; 730 731 if (end >= browser->top_idx + browser->rows) 732 end_row = browser->rows - 1; 733 else 734 end_row = end - browser->top_idx; 735 736 ui_browser__gotorc(browser, row, column); 737 SLsmg_draw_vline(end_row - row + 1); 738 739 ui_browser__gotorc(browser, end_row, column); 740 if (end < browser->top_idx + browser->rows) { 741 SLsmg_write_char(SLSMG_LLCORN_CHAR); 742 ui_browser__gotorc(browser, end_row, column + 1); 743 SLsmg_write_char(SLSMG_HLINE_CHAR); 744 ui_browser__gotorc(browser, end_row, column + 2); 745 SLsmg_write_char(SLSMG_RARROW_CHAR); 746 } 747 out: 748 SLsmg_set_char_set(0); 749 } 750 751 void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column, 752 u64 start, u64 end) 753 { 754 if (start > end) 755 __ui_browser__line_arrow_up(browser, column, start, end); 756 else 757 __ui_browser__line_arrow_down(browser, column, start, end); 758 } 759 760 void ui_browser__mark_fused(struct ui_browser *browser, unsigned int column, 761 unsigned int row, bool arrow_down) 762 { 763 unsigned int end_row; 764 765 if (row >= browser->top_idx) 766 end_row = row - browser->top_idx; 767 else 768 return; 769 770 SLsmg_set_char_set(1); 771 772 if (arrow_down) { 773 ui_browser__gotorc(browser, end_row, column - 1); 774 SLsmg_write_char(SLSMG_ULCORN_CHAR); 775 ui_browser__gotorc(browser, end_row, column); 776 SLsmg_draw_hline(2); 777 ui_browser__gotorc(browser, end_row + 1, column - 1); 778 SLsmg_write_char(SLSMG_LTEE_CHAR); 779 } else { 780 ui_browser__gotorc(browser, end_row, column - 1); 781 SLsmg_write_char(SLSMG_LTEE_CHAR); 782 ui_browser__gotorc(browser, end_row, column); 783 SLsmg_draw_hline(2); 784 } 785 786 SLsmg_set_char_set(0); 787 } 788 789 void ui_browser__init(void) 790 { 791 int i = 0; 792 793 perf_config(ui_browser__color_config, NULL); 794 795 while (ui_browser__colorsets[i].name) { 796 struct ui_browser_colorset *c = &ui_browser__colorsets[i++]; 797 sltt_set_color(c->colorset, c->name, c->fg, c->bg); 798 } 799 } 800