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