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