/* * Power debug tool (powerdebug) * * Copyright (C) 2016, Linaro Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include #include #include #define _GNU_SOURCE #include #undef _GNU_SOURCE #include #include #include #include #include #include "powerdebug.h" #include "mainloop.h" #include "display.h" enum { PT_COLOR_DEFAULT = 1, PT_COLOR_HEADER_BAR, PT_COLOR_ERROR, PT_COLOR_RED, PT_COLOR_YELLOW, PT_COLOR_GREEN, PT_COLOR_BRIGHT, PT_COLOR_BLUE, }; static WINDOW *header_win; static WINDOW *footer_win; static WINDOW *main_win; static int current_win; /* Number of lines in the virtual window */ static const int maxrows = 1024; struct rowdata { int attr; void *data; }; struct windata { WINDOW *pad; struct display_ops *ops; struct rowdata *rowdata; char *name; int nrdata; int scrolling; int cursor; }; /* Warning this is linked with the enum { CLOCK, REGULATOR, ... } */ struct windata windata[] = { [CLOCK] = { .name = "Clocks" }, [REGULATOR] = { .name = "Regulators" }, [SENSOR] = { .name = "Sensors" }, [GPIO] = { .name = "Gpio" }, [GENPD] = { .name = "Powerdomains" }, }; static void display_fini(void) { endwin(); } static int display_show_header(int win) { int i; int curr_pointer = 0; size_t array_size = sizeof(windata) / sizeof(windata[0]); wattrset(header_win, COLOR_PAIR(PT_COLOR_HEADER_BAR)); wbkgd(header_win, COLOR_PAIR(PT_COLOR_HEADER_BAR)); werase(header_win); mvwprintw(header_win, 0, curr_pointer, "PowerDebug %s", VERSION); curr_pointer += 20; for (i = 0; i < array_size; i++) { if (win == i) wattron(header_win, A_REVERSE); else wattroff(header_win, A_REVERSE); mvwprintw(header_win, 0, curr_pointer, " %s ", windata[i].name); curr_pointer += strlen(windata[i].name) + 2; } wrefresh(header_win); return 0; } #define footer_label " Q (Quit) R (Refresh) Other Keys: 'Left', " \ "'Right' , 'Up', 'Down', 'enter', , 'Esc'" static int display_show_footer(int win, char *string) { werase(footer_win); wattron(footer_win, A_REVERSE); mvwprintw(footer_win, 0, 0, "%s", string ? string : footer_label); wattroff(footer_win, A_REVERSE); wrefresh(footer_win); return 0; } static int display_refresh(int win, bool read) { /* we are trying to refresh a window which is not showed */ if (win != current_win) return 0; if (windata[win].ops && windata[win].ops->display) return windata[win].ops->display(read); if (werase(main_win)) return -1; return wrefresh(main_win); } int display_refresh_pad(int win) { int maxx, maxy; getmaxyx(stdscr, maxy, maxx); return prefresh(windata[win].pad, windata[win].scrolling, 0, 2, 0, maxy - 2, maxx); } void sigwinch_handler(int signo) { display_refresh(current_win, true); } static int display_show_unselection(int win, int line, bool bold) { if (mvwchgat(windata[win].pad, line, 0, -1, bold ? WA_BOLD: WA_NORMAL, 0, NULL) < 0) return -1; return display_refresh_pad(win); } void *display_get_row_data(int win) { return windata[win].rowdata[windata[win].cursor].data; } static int display_select(void) { if (windata[current_win].ops && windata[current_win].ops->select) return windata[current_win].ops->select(); return 0; } static int display_change(int keyvalue) { if (!(windata[current_win].nrdata)) return 0; if (windata[current_win].ops && windata[current_win].ops->change) return windata[current_win].ops->change(keyvalue); return 0; } static int display_next_panel(void) { size_t array_size = sizeof(windata) / sizeof(windata[0]); current_win++; current_win %= array_size; return current_win; } static int display_prev_panel(void) { size_t array_size = sizeof(windata) / sizeof(windata[0]); current_win--; if (current_win < 0) current_win = array_size - 1; return current_win; } static int display_next_line(void) { int maxy; int cursor = windata[current_win].cursor; int nrdata = windata[current_win].nrdata; int scrolling = windata[current_win].scrolling; struct rowdata *rowdata = windata[current_win].rowdata; maxy = getmaxy(stdscr); if (cursor >= nrdata) return cursor; display_show_unselection(current_win, cursor, rowdata[cursor].attr); if (cursor < nrdata - 1) { if (cursor >= (maxy - 4 + scrolling)) scrolling++; cursor++; } windata[current_win].scrolling = scrolling; windata[current_win].cursor = cursor; return cursor; } static int display_prev_line(void) { int cursor = windata[current_win].cursor; int nrdata = windata[current_win].nrdata; int scrolling = windata[current_win].scrolling; struct rowdata *rowdata = windata[current_win].rowdata; if (cursor >= nrdata) return cursor; display_show_unselection(current_win, cursor, rowdata[cursor].attr); if (cursor > 0) { if (cursor <= scrolling) scrolling--; cursor--; } windata[current_win].scrolling = scrolling; windata[current_win].cursor = cursor; return cursor; } static int display_set_row_data(int win, int line, void *data, int attr) { struct rowdata *rowdata = windata[win].rowdata; if (line >= windata[win].nrdata) { rowdata = realloc(rowdata, sizeof(struct rowdata) * (line + 1)); if (!rowdata) return -1; windata[win].nrdata = line + 1; } rowdata[line].data = data; rowdata[line].attr = attr; windata[win].rowdata = rowdata; return 0; } int display_reset_cursor(int win) { windata[win].nrdata = 0; werase(windata[win].pad); return wmove(windata[win].pad, 0, 0); } int display_print_line(int win, int line, char *str, int bold, void *data) { int attr = 0; if (bold) attr |= WA_BOLD; if (line == windata[win].cursor) attr |= WA_STANDOUT; if (display_set_row_data(win, line, data, attr)) return -1; if (attr) wattron(windata[win].pad, attr); wprintw(windata[win].pad, "%s\n", str); if (attr) wattroff(windata[win].pad, attr); return 0; } int display_print_error(int window, int line, char *str) { display_reset_cursor(window); display_print_line(window, line, str, 0, NULL); display_refresh_pad(window); return 0; } static int display_find_keystroke(int fd, void *data); struct find_data { size_t len; char *string; regex_t *reg; int ocursor; int oscrolling; }; struct find_data *display_find_init(void) { const char *regexp = "^[a-z|0-9|_|-|.]"; struct find_data *findd; const size_t len = 64; regex_t *reg; char *search4; reg = malloc(sizeof(*reg)); if (!reg) return NULL; if (regcomp(reg, regexp, REG_ICASE)) goto out_free_reg; search4 = malloc(len); if (!search4) goto out_free_regcomp; memset(search4, '\0', len); findd = malloc(sizeof(*findd)); if (!findd) goto out_free_search4; findd->string = search4; findd->reg = reg; findd->len = len; /* save the location of the cursor on the main window in order to * browse the search result */ findd->ocursor = windata[current_win].cursor; findd->oscrolling = windata[current_win].scrolling; windata[current_win].cursor = 0; windata[current_win].scrolling = 0; curs_set(1); out: return findd; out_free_search4: free(search4); out_free_regcomp: regfree(reg); out_free_reg: free(reg); goto out; } static void display_find_fini(struct find_data *findd) { windata[current_win].cursor = findd->ocursor; windata[current_win].scrolling = findd->oscrolling; regfree(findd->reg); free(findd->string); free(findd); curs_set(0); } static int display_switch_to_find(int fd) { struct find_data *findd; findd = display_find_init(); if (!findd) return -1; if (mainloop_del(fd)) return -1; if (mainloop_add(fd, display_find_keystroke, findd)) return -1; if (display_show_footer(current_win, "find (esc to exit)?")) return -1; return 0; } static int display_keystroke(int fd, void *data) { int keystroke = getch(); switch (keystroke) { case KEY_RIGHT: case '\t': display_show_header(display_next_panel()); break; case KEY_LEFT: case KEY_BTAB: display_show_header(display_prev_panel()); break; case KEY_DOWN: display_next_line(); break; case KEY_UP: display_prev_line(); break; case '\n': case '\r': display_select(); break; case 'v': case 'V': case 'd': case 'D': display_change(toupper(keystroke)); break; case EOF: case 'q': case 'Q': return 1; case '/': return display_switch_to_find(fd); case 'r': case 'R': return display_refresh(current_win, true); default: return 0; } display_refresh(current_win, false); return 0; } static int display_switch_to_main(int fd) { if (mainloop_del(fd)) return -1; if (mainloop_add(fd, display_keystroke, NULL)) return -1; if (display_show_header(current_win)) return -1; if (display_show_footer(current_win, NULL)) return -1; return display_refresh(current_win, false); } static int display_find_keystroke(int fd, void *data) { struct find_data *findd = data; regex_t *reg = findd->reg; char *string = findd->string; int keystroke = getch(); char match[2] = { [0] = (char)keystroke, [1] = '\0' }; regmatch_t m[1]; switch (keystroke) { case '\e': display_find_fini(findd); return display_switch_to_main(fd); case KEY_DOWN: display_next_line(); break; case KEY_UP: display_prev_line(); break; case KEY_BACKSPACE: if (strlen(string)) string[strlen(string) - 1] = '\0'; windata[current_win].cursor = 0; windata[current_win].scrolling = 0; break; case '\n': case '\r': if (!windata[current_win].ops || !windata[current_win].ops->selectf) return 0; if (windata[current_win].ops->selectf()) return -1; windata[current_win].cursor = 0; windata[current_win].scrolling = 0; return 0; default: /* We don't want invalid characters for a name */ if (regexec(reg, match, 1, m, 0)) return 0; if (strlen(string) < findd->len - 1) string[strlen(string)] = (char)keystroke; windata[current_win].cursor = 0; windata[current_win].scrolling = 0; break; } if (!windata[current_win].ops || !windata[current_win].ops->find) return 0; if (windata[current_win].ops->find(string)) return -1; if (display_show_header(current_win)) return -1; if (display_show_footer(current_win, strlen(string) ? string : "find (esc to exit)?")) return -1; return 0; } int display_init(struct powerdebug_options *options) { int i, maxx, maxy; size_t array_size = sizeof(windata) / sizeof(windata[0]); current_win = (ffs(options->flags & DEFAULT_OPTION) - 1); signal(SIGWINCH, sigwinch_handler); if (mainloop_add(0, display_keystroke, NULL)) return -1; if (!initscr()) return -1; start_color(); use_default_colors(); keypad(stdscr, TRUE); noecho(); cbreak(); curs_set(0); nonl(); if (can_change_color()) { if (init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK) || init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED) || init_pair(PT_COLOR_HEADER_BAR, COLOR_WHITE, COLOR_BLACK) || init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW) || init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN) || init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK) || init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE) || init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED)) return -1; } if (atexit(display_fini)) return -1; getmaxyx(stdscr, maxy, maxx); for (i = 0; i < array_size; i++) { main_win = subwin(stdscr, maxy - 2, maxx, 1, 0); if (!main_win) return -1; windata[i].pad = newpad(maxrows, maxx); if (!windata[i].pad) return -1; } header_win = subwin(stdscr, 1, maxx, 0, 0); if (!header_win) return -1; footer_win = subwin(stdscr, 1, maxx, maxy-1, 0); if (!footer_win) return -1; if (display_show_header(current_win)) return -1; if (display_show_footer(current_win, NULL)) return -1; return display_refresh(current_win, true); } int display_column_name(const char *line) { werase(main_win); wattron(main_win, A_BOLD); mvwprintw(main_win, 0, 0, "%s", line); wattroff(main_win, A_BOLD); wrefresh(main_win); return 0; } int display_register(int win, struct display_ops *ops) { size_t array_size = sizeof(windata) / sizeof(windata[0]); if (win < 0 || win >= array_size) { printf("error: invalid window"); return -1; } windata[win].ops = ops; return 0; }