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