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