summaryrefslogtreecommitdiffstats
path: root/src/out_curses.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/out_curses.c')
-rw-r--r--src/out_curses.c1277
1 files changed, 1277 insertions, 0 deletions
diff --git a/src/out_curses.c b/src/out_curses.c
new file mode 100644
index 0000000..602998c
--- /dev/null
+++ b/src/out_curses.c
@@ -0,0 +1,1277 @@
+/*
+ * out_curses.c Curses Output
+ *
+ * Copyright (c) 2001-2013 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <bmon/bmon.h>
+#include <bmon/conf.h>
+#include <bmon/attr.h>
+#include <bmon/element.h>
+#include <bmon/element_cfg.h>
+#include <bmon/input.h>
+#include <bmon/history.h>
+#include <bmon/graph.h>
+#include <bmon/output.h>
+#include <bmon/utils.h>
+
+enum {
+ GRAPH_DISPLAY_SIDE_BY_SIDE = 1,
+ GRAPH_DISPLAY_STANDARD = 2,
+};
+
+enum {
+ KEY_TOGGLE_LIST = 'l',
+ KEY_TOGGLE_GRAPH = 'g',
+ KEY_TOGGLE_DETAILS = 'd',
+ KEY_TOGGLE_INFO = 'i',
+ KEY_COLLECT_HISTORY = 'h',
+};
+
+#define DETAILS_COLS 40
+
+#define LIST_COL_1 31
+#define LIST_COL_2 55
+
+/* Set to element_current() before drawing */
+static struct element *current_element;
+
+static struct attr *current_attr;
+
+/* Length of list to draw, updated in draw_content() */
+static int list_length;
+
+static int list_req;
+
+/* Number of graphs to draw (may be < c_ngraph) */
+static int ngraph;
+
+/*
+ * Offset in number of lines within the the element list of the currently
+ * selected element. Updated while summing up required lines.
+ */
+static unsigned int selection_offset;
+
+/*
+ * Offset in number of lines of the first element to be drawn. Updated
+ * in draw_content()
+ */
+static int offset;
+
+/*
+ * Offset to the first graph to draw in number of attributes with graphs.
+ */
+static unsigned int graph_offset;
+
+static int graph_display = GRAPH_DISPLAY_STANDARD;
+
+/*
+ * Number of detail columns
+ */
+static int detail_cols;
+static int info_cols;
+
+static int initialized;
+static int print_help;
+static int quit_mode;
+static int help_page;
+
+/* Current row */
+static int row;
+
+/* Number of rows */
+static int rows;
+
+/* Number of columns */
+static int cols;
+
+static int c_show_graph = 1;
+static int c_ngraph = 1;
+static int c_use_colors = 1;
+static int c_show_details = 0;
+static int c_show_list = 1;
+static int c_show_info = 0;
+static int c_list_min = 6;
+
+static struct graph_cfg c_graph_cfg = {
+ .gc_width = 60,
+ .gc_height = 6,
+ .gc_foreground = '|',
+ .gc_background = '.',
+ .gc_noise = ':',
+ .gc_unknown = '?',
+};
+
+#define NEXT_ROW() \
+ do { \
+ row++; \
+ if (row >= rows - 1) \
+ return; \
+ move(row, 0); \
+ } while(0)
+
+static void apply_layout(int layout)
+{
+ if (c_use_colors)
+ attrset(COLOR_PAIR(layout) | cfg_layout[layout].l_attr);
+ else
+ attrset(cfg_layout[layout].l_attr);
+}
+
+char *float2str(double value, int width, int prec, char *buf, size_t len)
+{
+ snprintf(buf, len, "%'*.*f", width, value == 0.0f ? 0 : prec, value);
+
+ return buf;
+}
+
+static void put_line(const char *fmt, ...)
+{
+ va_list args;
+ char buf[2048];
+ int x, y;
+
+ memset(buf, 0, sizeof(buf));
+ getyx(stdscr, y, x);
+
+ va_start(args, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+
+ if (strlen(buf) > cols-x)
+ buf[cols - x] = '\0';
+ else
+ memset(&buf[strlen(buf)], ' ', cols - strlen(buf)-x);
+
+ addstr(buf);
+}
+
+static void center_text(const char *fmt, ...)
+{
+ va_list args;
+ char *str;
+ unsigned int col;
+
+ va_start(args, fmt);
+ vasprintf(&str, fmt, args);
+ va_end(args);
+
+ col = (cols / 2) - (strlen(str) / 2);
+ if (col > cols - 1)
+ col = cols - 1;
+
+ move(row, col);
+ addstr(str);
+ move(row, 0);
+
+ free(str);
+}
+
+static int curses_init(void)
+{
+ if (!initscr()) {
+ fprintf(stderr, "Unable to initialize curses screen\n");
+ return -EOPNOTSUPP;
+ }
+
+ initialized = 1;
+
+ if (!has_colors())
+ c_use_colors = 0;
+
+ if (c_use_colors) {
+ int i;
+
+ start_color();
+
+#if defined HAVE_USE_DEFAULT_COLORS
+ use_default_colors();
+#endif
+ for (i = 1; i < LAYOUT_MAX+1; i++)
+ init_pair(i, cfg_layout[i].l_fg, cfg_layout[i].l_bg);
+ }
+
+ keypad(stdscr, TRUE);
+ nonl();
+ cbreak();
+ noecho();
+ nodelay(stdscr, TRUE); /* getch etc. must be non-blocking */
+ clear();
+ curs_set(0);
+
+ return 0;
+}
+
+static void curses_shutdown(void)
+{
+ if (initialized)
+ endwin();
+}
+
+struct detail_arg
+{
+ int nattr;
+};
+
+static void draw_attr_detail(struct element *e, struct attr *a, void *arg)
+{
+ char *rx_u, *tx_u, buf1[32], buf2[32];
+ int rxprec, txprec, ncol;
+ struct detail_arg *da = arg;
+
+ double rx = unit_value2str(a->a_rx_rate.r_total,
+ a->a_def->ad_unit,
+ &rx_u, &rxprec);
+ double tx = unit_value2str(a->a_tx_rate.r_total,
+ a->a_def->ad_unit,
+ &tx_u, &txprec);
+
+ if (da->nattr >= detail_cols) {
+ NEXT_ROW();
+ da->nattr = 0;
+ }
+
+ ncol = (da->nattr * DETAILS_COLS) - 1;
+ move(row, ncol);
+ if (ncol > 0)
+ addch(ACS_VLINE);
+
+ put_line(" %-14.14s %8s%-3s %8s%-3s\n",
+ a->a_def->ad_description,
+ (a->a_flags & ATTR_RX_ENABLED) ?
+ float2str(rx, 8, rxprec, buf1, sizeof(buf1)) : "-", rx_u,
+ (a->a_flags & ATTR_TX_ENABLED) ?
+ float2str(tx, 8, txprec, buf2, sizeof(buf2)) : "-", tx_u);
+
+ da->nattr++;
+}
+
+static void draw_details(void)
+{
+ int i;
+ struct detail_arg arg = {
+ .nattr = 0,
+ };
+
+ if (!current_element->e_nattrs)
+ return;
+
+ for (i = 1; i < detail_cols; i++)
+ mvaddch(row, (i * DETAILS_COLS) - 1, ACS_TTEE);
+
+ NEXT_ROW();
+ put_line("");
+ for (i = 0; i < detail_cols; i++) {
+ if (i > 0)
+ mvaddch(row, (i * DETAILS_COLS) - 1, ACS_VLINE);
+ move(row, (i * DETAILS_COLS) + 22);
+ put_line("RX TX");
+ }
+
+ NEXT_ROW();
+ element_foreach_attr(current_element, draw_attr_detail, &arg);
+
+ /*
+ * If the last row was incomplete, not all vlines have been drawn.
+ * draw them here
+ */
+ for (i = 1; i < detail_cols; i++)
+ mvaddch(row, (i * DETAILS_COLS - 1), ACS_VLINE);
+}
+
+static void print_message(const char *text)
+{
+ int i, y = (rows/2) - 2;
+ int len = strlen(text);
+ int x = (cols/2) - (len / 2);
+
+ attrset(A_STANDOUT);
+ mvaddch(y - 2, x - 1, ACS_ULCORNER);
+ mvaddch(y + 2, x - 1, ACS_LLCORNER);
+ mvaddch(y - 2, x + len, ACS_URCORNER);
+ mvaddch(y + 2, x + len, ACS_LRCORNER);
+
+ for (i = 0; i < 3; i++) {
+ mvaddch(y - 1 + i, x + len, ACS_VLINE);
+ mvaddch(y - 1 + i, x - 1 ,ACS_VLINE);
+ }
+
+ for (i = 0; i < len; i++) {
+ mvaddch(y - 2, x + i, ACS_HLINE);
+ mvaddch(y - 1, x + i, ' ');
+ mvaddch(y + 1, x + i, ' ');
+ mvaddch(y + 2, x + i, ACS_HLINE);
+ }
+
+ mvaddstr(y, x, text);
+ attroff(A_STANDOUT);
+
+ row = y + 2;
+}
+
+static void draw_help(void)
+{
+#define HW 46
+#define HH 19
+ int i, y = (rows/2) - (HH/2);
+ int x = (cols/2) - (HW/2);
+ char pad[HW+1];
+
+ memset(pad, ' ', sizeof(pad));
+ pad[sizeof(pad) - 1] = '\0';
+
+ attron(A_STANDOUT);
+
+ for (i = 0; i < HH; i++)
+ mvaddnstr(y + i, x, pad, -1);
+
+ mvaddch(y - 1, x - 1, ACS_ULCORNER);
+ mvaddch(y + HH, x - 1, ACS_LLCORNER);
+
+ mvaddch(y - 1, x + HW, ACS_URCORNER);
+ mvaddch(y + HH, x + HW, ACS_LRCORNER);
+
+ for (i = 0; i < HH; i++) {
+ mvaddch(y + i, x - 1, ACS_VLINE);
+ mvaddch(y + i, x + HW, ACS_VLINE);
+ }
+
+ for (i = 0; i < HW; i++) {
+ mvaddch(y - 1, x + i, ACS_HLINE);
+ mvaddch(y + HH, x + i, ACS_HLINE);
+ }
+
+ attron(A_BOLD);
+ mvaddnstr(y- 1, x+15, "QUICK REFERENCE", -1);
+ attron(A_UNDERLINE);
+ mvaddnstr(y+ 0, x+1, "Navigation", -1);
+ attroff(A_BOLD | A_UNDERLINE);
+
+ mvaddnstr(y+ 1, x+3, "Up, Down Previous/Next element", -1);
+ mvaddnstr(y+ 2, x+3, "PgUp, PgDown Scroll up/down entire page", -1);
+ mvaddnstr(y+ 3, x+3, "Left, Right Previous/Next attribute", -1);
+ mvaddnstr(y+ 4, x+3, "[, ] Previous/Next group", -1);
+ mvaddnstr(y+ 5, x+3, "? Toggle quick reference", -1);
+ mvaddnstr(y+ 6, x+3, "q Quit bmon", -1);
+
+ attron(A_BOLD | A_UNDERLINE);
+ mvaddnstr(y+ 8, x+1, "Display Settings", -1);
+ attroff(A_BOLD | A_UNDERLINE);
+
+ mvaddnstr(y+ 9, x+3, "d Toggle detailed statistics", -1);
+ mvaddnstr(y+10, x+3, "l Toggle element list", -1);
+ mvaddnstr(y+11, x+3, "i Toggle additional info", -1);
+
+ attron(A_BOLD | A_UNDERLINE);
+ mvaddnstr(y+13, x+1, "Graph Settings", -1);
+ attroff(A_BOLD | A_UNDERLINE);
+
+ mvaddnstr(y+14, x+3, "g Toggle graphical statistics", -1);
+ mvaddnstr(y+15, x+3, "H Start recording history data", -1);
+ mvaddnstr(y+16, x+3, "TAB Switch time unit of graph", -1);
+ mvaddnstr(y+17, x+3, "<, > Change number of graphs", -1);
+
+ attroff(A_STANDOUT);
+
+ row = y + HH;
+}
+
+static int lines_required_for_header(void)
+{
+ return 1;
+}
+
+static void draw_header(void)
+{
+ apply_layout(LAYOUT_STATUSBAR);
+
+ if (current_element)
+ put_line(" %s %c%s%c",
+ current_element->e_name,
+ current_element->e_description ? '(' : ' ',
+ current_element->e_description ? : "",
+ current_element->e_description ? ')' : ' ');
+ else
+ put_line("");
+
+ move(row, COLS - strlen(PACKAGE_STRING) - 1);
+ put_line("%s", PACKAGE_STRING);
+ move(row, 0);
+}
+
+static int lines_required_for_statusbar(void)
+{
+ return 1;
+}
+
+static void draw_statusbar(void)
+{
+ static const char *help_text = "Press ? for help";
+ char s[27];
+ time_t t = time(0);
+
+ apply_layout(LAYOUT_STATUSBAR);
+
+ asctime_r(localtime(&t), s);
+ s[strlen(s) - 1] = '\0';
+
+ row = rows-1;
+ move(row, 0);
+ put_line(" %s", s);
+
+ move(row, COLS - strlen(help_text) - 1);
+ put_line("%s", help_text);
+
+ move(row, 0);
+}
+
+static void count_attr_graph(struct element *g, struct attr *a, void *arg)
+{
+ if (a == current_attr)
+ graph_offset = ngraph;
+
+ ngraph++;
+}
+
+static int lines_required_for_graph(void)
+{
+ int lines = 0;
+
+ ngraph = 0;
+
+ if (c_show_graph && current_element) {
+ graph_display = GRAPH_DISPLAY_STANDARD;
+
+ element_foreach_attr(current_element, count_attr_graph, NULL);
+
+ if (ngraph > c_ngraph)
+ ngraph = c_ngraph;
+
+ /* check if we have room to draw graphs on the same level */
+ if (cols > (2 * (c_graph_cfg.gc_width + 10)))
+ graph_display = GRAPH_DISPLAY_SIDE_BY_SIDE;
+
+ /* +2 = header + time axis */
+ lines = ngraph * (graph_display * (c_graph_cfg.gc_height + 2));
+ }
+
+ return lines + 1;
+}
+
+static int lines_required_for_details(void)
+{
+ int lines = 1;
+
+ if (c_show_details && current_element) {
+ lines++; /* header */
+
+ detail_cols = cols / DETAILS_COLS;
+
+ if (!detail_cols)
+ detail_cols = 1;
+
+ lines += (current_element->e_nattrs / detail_cols);
+ if (current_element->e_nattrs % detail_cols)
+ lines++;
+ }
+
+ return lines;
+}
+
+static void count_element_lines(struct element_group *g, struct element *e,
+ void *arg)
+{
+ int *lines = arg;
+
+ if (e == current_element)
+ selection_offset = *lines;
+
+ (*lines)++;
+}
+
+static void count_group_lines(struct element_group *g, void *arg)
+{
+ int *lines = arg;
+
+ /* group title */
+ (*lines)++;
+
+ group_foreach_element(g, &count_element_lines, arg);
+}
+
+static int lines_required_for_list(void)
+{
+ int lines = 0;
+
+ if (c_show_list)
+ group_foreach(&count_group_lines, &lines);
+ else
+ lines = 1;
+
+ return lines;
+}
+
+static inline int line_visible(int line)
+{
+ return line >= offset && line < (offset + list_length);
+}
+
+static void draw_attr(double rate1, int prec1, char *unit1,
+ double rate2, int prec2, char *unit2,
+ float usage, int ncol)
+{
+ char buf[32];
+
+ move(row, ncol);
+ addch(ACS_VLINE);
+ printw("%7s%-3s",
+ float2str(rate1, 7, prec1, buf, sizeof(buf)), unit1);
+
+ printw("%7s%-3s",
+ float2str(rate2, 7, prec2, buf, sizeof(buf)), unit2);
+
+ if (usage != FLT_MAX)
+ printw("%2.0f%%", usage);
+ else
+ printw("%3s", "");
+}
+
+static void draw_element(struct element_group *g, struct element *e,
+ void *arg)
+{
+ int *line = arg;
+
+ apply_layout(LAYOUT_LIST);
+
+ if (line_visible(*line)) {
+ char *rxu1 = "", *txu1 = "", *rxu2 = "", *txu2 = "";
+ double rx1 = 0.0f, tx1 = 0.0f, rx2 = 0.0f, tx2 = 0.0f;
+ char pad[IFNAMSIZ + 32];
+ int rx1prec = 0, tx1prec = 0, rx2prec = 0, tx2prec = 0;
+ struct attr *a;
+
+ NEXT_ROW();
+
+ if (e->e_key_attr[GT_MAJOR] &&
+ (a = attr_lookup(e, e->e_key_attr[GT_MAJOR]->ad_id)))
+ attr_rate2float(a, &rx1, &rxu1, &rx1prec,
+ &tx1, &txu1, &tx1prec);
+
+ if (e->e_key_attr[GT_MINOR] &&
+ (a = attr_lookup(e, e->e_key_attr[GT_MINOR]->ad_id)))
+ attr_rate2float(a, &rx2, &rxu2, &rx2prec,
+ &tx2, &txu2, &tx2prec);
+
+ memset(pad, 0, sizeof(pad));
+ memset(pad, ' ', e->e_level < 6 ? e->e_level * 2 : 12);
+
+ strncat(pad, e->e_name, sizeof(pad) - strlen(pad) - 1);
+
+ if (e->e_description) {
+ strncat(pad, " (", sizeof(pad) - strlen(pad) - 1);
+ strncat(pad, e->e_description, sizeof(pad) - strlen(pad) - 1);
+ strncat(pad, ")", sizeof(pad) - strlen(pad) - 1);
+ }
+
+ if (*line == offset) {
+ attron(A_BOLD);
+ addch(ACS_UARROW);
+ attroff(A_BOLD);
+ addch(' ');
+ } else if (e == current_element) {
+ apply_layout(LAYOUT_SELECTED);
+ printw("->");
+ apply_layout(LAYOUT_LIST);
+ } else if (*line == offset + list_length - 1 &&
+ *line < (list_req - 1)) {
+ attron(A_BOLD);
+ addch(ACS_DARROW);
+ attroff(A_BOLD);
+ addch(' ');
+ } else
+ printw(" ");
+
+ put_line("%-30.30s", pad);
+
+ draw_attr(rx1, rx1prec, rxu1, rx2, rx2prec, rxu2,
+ e->e_rx_usage, LIST_COL_1);
+
+ draw_attr(tx1, tx1prec, txu1, tx2, tx2prec, txu2,
+ e->e_tx_usage, LIST_COL_2);
+
+ }
+
+ (*line)++;
+}
+
+static void draw_group(struct element_group *g, void *arg)
+{
+ int *line = arg;
+
+ if (line_visible(*line)) {
+ NEXT_ROW();
+ attron(A_BOLD);
+ put_line("%s", g->g_hdr->gh_title);
+
+ attroff(A_BOLD);
+ mvaddch(row, LIST_COL_1, ACS_VLINE);
+ attron(A_BOLD);
+ put_line("%7s %7s %%",
+ g->g_hdr->gh_column[0],
+ g->g_hdr->gh_column[1]);
+
+ attroff(A_BOLD);
+ mvaddch(row, LIST_COL_2, ACS_VLINE);
+ attron(A_BOLD);
+ put_line("%7s %7s %%",
+ g->g_hdr->gh_column[2],
+ g->g_hdr->gh_column[3]);
+ }
+
+ (*line)++;
+
+ group_foreach_element(g, draw_element, arg);
+}
+
+static void draw_element_list(void)
+{
+ int line = 0;
+
+ group_foreach(draw_group, &line);
+}
+
+static inline int attr_visible(int nattr)
+{
+ return nattr >= graph_offset && nattr < (graph_offset + ngraph);
+}
+
+static void draw_graph_centered(struct graph *g, int row, int ncol,
+ const char *text)
+{
+ int hcenter = (g->g_cfg.gc_width / 2) - (strlen(text) / 2) + 8;
+
+ if (hcenter < 9)
+ hcenter = 9;
+
+ mvprintw(row, ncol + hcenter, "%.*s", g->g_cfg.gc_width, text);
+}
+
+static void draw_table(struct graph *g, struct graph_table *tbl,
+ struct attr *a, struct history *h,
+ const char *hdr, int ncol)
+{
+ int i, save_row;
+ char buf[32];
+
+ if (!tbl->gt_table) {
+ for (i = g->g_cfg.gc_height; i >= 0; i--) {
+ move(++row, ncol);
+ put_line("");
+ }
+ return;
+ }
+
+ move(++row, ncol);
+ put_line("%8s", tbl->gt_y_unit ? : "");
+
+ snprintf(buf, sizeof(buf), "(%s %s/%s)",
+ hdr, a->a_def->ad_description,
+ h ? h->h_definition->hd_name : "?");
+
+ draw_graph_centered(g, row, ncol, buf);
+
+ //move(row, ncol + g->g_cfg.gc_width - 3);
+ //put_line("[err %.2f%%]", rtiming.rt_variance.v_error);
+
+ for (i = (g->g_cfg.gc_height - 1); i >= 0; i--) {
+ move(++row, ncol);
+ put_line("%'8.2f %s",
+ tbl->gt_scale[i],
+ tbl->gt_table + (i * graph_row_size(&g->g_cfg)));
+ }
+
+ move(++row, ncol);
+ put_line(" 1");
+
+ for (i = 1; i <= g->g_cfg.gc_width; i++) {
+ if (i % 5 == 0) {
+ move(row, ncol + i + 7);
+ printw("%2d", i);
+ }
+ }
+
+ if (!h) {
+ const char *t1 = " No history data available. ";
+ const char *t2 = " Press h to start collecting history. ";
+ int vcenter = g->g_cfg.gc_height / 2;
+
+ save_row = row;
+ draw_graph_centered(g, save_row - vcenter - 1, ncol, t1);
+ draw_graph_centered(g, save_row - vcenter, ncol, t2);
+ row = save_row;
+ }
+}
+
+static void draw_history_graph(struct attr *a, struct history *h)
+{
+ struct graph *g;
+ int ncol = 0, save_row;
+
+ g = graph_alloc(h, &c_graph_cfg);
+ graph_refill(g, h);
+
+ save_row = row;
+ draw_table(g, &g->g_rx, a, h, "RX", ncol);
+
+ if (graph_display == GRAPH_DISPLAY_SIDE_BY_SIDE) {
+ ncol = cols / 2;
+ row = save_row;
+ }
+
+ draw_table(g, &g->g_tx, a, h, "TX", ncol);
+
+ graph_free(g);
+}
+
+static void draw_attr_graph(struct element *e, struct attr *a, void *arg)
+{
+ int *nattr = arg;
+
+ if (attr_visible(*nattr)) {
+ struct history_def *sel;
+ struct history *h;
+
+ sel = history_current();
+ c_graph_cfg.gc_unit = a->a_def->ad_unit;
+
+ list_for_each_entry(h, &a->a_history_list, h_list) {
+ if (h->h_definition != sel)
+ continue;
+
+ draw_history_graph(a, h);
+ goto out;
+ }
+
+ draw_history_graph(a, NULL);
+ }
+
+out:
+ (*nattr)++;
+}
+
+static void draw_graph(void)
+{
+ int nattr = 0;
+
+ element_foreach_attr(current_element, &draw_attr_graph, &nattr);
+}
+
+static int lines_required_for_info(void)
+{
+ int lines = 1;
+
+ if (c_show_info) {
+ info_cols = cols / DETAILS_COLS;
+
+ if (!info_cols)
+ info_cols = 1;
+
+ lines += (current_element->e_ninfo / info_cols);
+ if (current_element->e_ninfo % info_cols)
+ lines++;
+ }
+
+ return lines;
+}
+
+static void __draw_info(struct element *e, struct info *info, int *ninfo)
+{
+ int ncol;
+
+ ncol = ((*ninfo) * DETAILS_COLS) - 1;
+ move(row, ncol);
+ if (ncol > 0)
+ addch(ACS_VLINE);
+
+ put_line(" %-14.14s %22.22s", info->i_name, info->i_value);
+
+ if (++(*ninfo) >= info_cols) {
+ NEXT_ROW();
+ *ninfo = 0;
+ }
+}
+
+static void draw_info(void)
+{
+ struct info *info;
+ int i, ninfo = 0;
+
+ if (!current_element->e_ninfo)
+ return;
+
+ for (i = 1; i < detail_cols; i++)
+ mvaddch(row, (i * DETAILS_COLS) - 1,
+ c_show_details ? ACS_PLUS : ACS_TTEE);
+
+ NEXT_ROW();
+ list_for_each_entry(info, &current_element->e_info_list, i_list)
+ __draw_info(current_element, info, &ninfo);
+
+ /*
+ * If the last row was incomplete, not all vlines have been drawn.
+ * draw them here
+ */
+ for (i = 1; i < info_cols; i++)
+ mvaddch(row, (i * DETAILS_COLS - 1), ACS_VLINE);
+}
+
+static void draw_content(void)
+{
+ int graph_req, details_req, lines_available, total_req;
+ int info_req, empty_lines;
+ int disable_graph = 0, disable_details = 0, disable_info = 0;
+
+ if (!current_element)
+ return;
+
+ /*
+ * Reset selection offset. Will be set in lines_required_for_list().
+ */
+ selection_offset = 0;
+ offset = 0;
+
+ /* Reset graph offset, will be set in lines_required_for_graph() */
+ graph_offset = 0;
+
+ lines_available = rows - lines_required_for_statusbar()
+ - lines_required_for_header();
+
+ list_req = lines_required_for_list();
+ graph_req = lines_required_for_graph();
+ details_req = lines_required_for_details();
+ info_req = lines_required_for_info();
+
+ total_req = list_req + graph_req + details_req + info_req;
+
+ if (total_req <= lines_available) {
+ /*
+ * Enough lines available for all data to displayed, all
+ * is good. Display the full list.
+ */
+ list_length = list_req;
+ goto draw;
+ }
+
+ /*
+ * Not enough lines available for full list and all details
+ * requested...
+ */
+
+ if (c_show_list) {
+ /*
+ * ... try shortening the list first.
+ */
+ list_length = lines_available - (total_req - list_req);
+ if (list_length >= c_list_min)
+ goto draw;
+ }
+
+ if (c_show_info) {
+ /* try disabling info */
+ list_length = lines_available - (total_req - info_req + 1);
+ if (list_length >= c_list_min) {
+ disable_info = 1;
+ goto draw;
+ }
+ }
+
+ if (c_show_details) {
+ /* ... try disabling details */
+ list_length = lines_available - (total_req - details_req + 1);
+ if (list_length >= c_list_min) {
+ disable_details = 1;
+ goto draw;
+ }
+ }
+
+ /* ... try disabling graph, details, and info */
+ list_length = lines_available - 1 - 1 - 1;
+ if (list_length >= c_list_min) {
+ disable_graph = 1;
+ disable_details = 1;
+ disable_info = 1;
+ goto draw;
+ }
+
+ NEXT_ROW();
+ put_line("A minimum of %d lines is required to display content.\n",
+ (rows - lines_available) + c_list_min + 2);
+ return;
+
+draw:
+ if (selection_offset && list_length > 0) {
+ /*
+ * Vertically align the selected element in the middle
+ * of the list.
+ */
+ offset = selection_offset - (list_length / 2);
+
+ /*
+ * If element 0..(list_length/2) is selected, offset is
+ * negative here. Start drawing from first element.
+ */
+ if (offset < 0)
+ offset = 0;
+
+ /*
+ * Ensure the full list length is used if one of the
+ * last (list_length/2) elements is selected.
+ */
+ if (offset > (list_req - list_length))
+ offset = (list_req - list_length);
+
+ if (offset >= list_req)
+ BUG();
+ }
+
+ if (c_show_list) {
+ draw_element_list();
+ } else {
+ NEXT_ROW();
+ hline(ACS_HLINE, cols);
+ center_text(" Press %c to enable list view ",
+ KEY_TOGGLE_LIST);
+ }
+
+ /*
+ * Graphical statistics
+ */
+ NEXT_ROW();
+ hline(ACS_HLINE, cols);
+ mvaddch(row, LIST_COL_1, ACS_BTEE);
+ mvaddch(row, LIST_COL_2, ACS_BTEE);
+
+ if (!c_show_graph)
+ center_text(" Press %c to enable graphical statistics ",
+ KEY_TOGGLE_GRAPH);
+ else {
+ if (disable_graph)
+ center_text(" Increase screen height to see graphical statistics ");
+ else
+ draw_graph();
+ }
+
+ empty_lines = rows - row - details_req - info_req
+ - lines_required_for_statusbar() - 1;
+
+ while (empty_lines-- > 0) {
+ NEXT_ROW();
+ put_line("");
+ }
+
+ /*
+ * Detailed statistics
+ */
+ NEXT_ROW();
+ hline(ACS_HLINE, cols);
+
+ if (!c_show_details)
+ center_text(" Press %c to enable detailed statistics ",
+ KEY_TOGGLE_DETAILS);
+ else {
+ if (disable_details)
+ center_text(" Increase screen height to see detailed statistics ");
+ else
+ draw_details();
+ }
+
+ /*
+ * Additional information
+ */
+ NEXT_ROW();
+ hline(ACS_HLINE, cols);
+
+ if (!c_show_info)
+ center_text(" Press %c to enable additional information ",
+ KEY_TOGGLE_INFO);
+ else {
+ if (disable_info)
+ center_text(" Increase screen height to see additional information ");
+ else
+ draw_info();
+ }
+}
+
+
+static void curses_draw(void)
+{
+ row = 0;
+ move(0,0);
+
+ getmaxyx(stdscr, rows, cols);
+
+ if (rows < 4) {
+ clear();
+ put_line("Screen must be at least 4 rows in height");
+ goto out;
+ }
+
+ if (cols < 48) {
+ clear();
+ put_line("Screen must be at least 48 columns width");
+ goto out;
+ }
+
+ current_element = element_current();
+ current_attr = attr_current();
+
+ draw_header();
+
+ apply_layout(LAYOUT_DEFAULT);
+ draw_content();
+
+ /* fill empty lines with blanks */
+ while (row < (rows - 1 - lines_required_for_statusbar())) {
+ move(++row, 0);
+ put_line("");
+ }
+
+ draw_statusbar();
+
+ if (quit_mode)
+ print_message(" Really Quit? (y/n) ");
+ else if (print_help) {
+ if (help_page == 0)
+ draw_help();
+#if 0
+ else
+ draw_help_2();
+#endif
+ }
+
+out:
+ attrset(0);
+ refresh();
+}
+
+static int handle_input(int ch)
+{
+ switch (ch)
+ {
+ case 'q':
+ if (print_help)
+ print_help = 0;
+ else
+ quit_mode = quit_mode ? 0 : 1;
+ return 1;
+
+ case 0x1b:
+ quit_mode = 0;
+ print_help = 0;
+ return 1;
+
+ case 'y':
+ if (quit_mode)
+ exit(0);
+ break;
+
+ case 'n':
+ if (quit_mode)
+ quit_mode = 0;
+ return 1;
+
+ case 12:
+ case KEY_CLEAR:
+#ifdef HAVE_REDRAWWIN
+ redrawwin(stdscr);
+#endif
+ clear();
+ return 1;
+
+ case '?':
+ clear();
+ print_help = 1;
+ return 1;
+
+ case KEY_TOGGLE_GRAPH:
+ c_show_graph = !c_show_graph;
+ if (c_show_graph && !c_ngraph)
+ c_ngraph = 1;
+ return 1;
+
+ case KEY_TOGGLE_DETAILS:
+ c_show_details = !c_show_details;
+ return 1;
+
+ case KEY_TOGGLE_LIST:
+ c_show_list = !c_show_list;
+ return 1;
+
+ case KEY_TOGGLE_INFO:
+ c_show_info = !c_show_info;
+ return 1;
+
+ case KEY_COLLECT_HISTORY:
+ if (current_attr) {
+ attr_start_collecting_history(current_attr);
+ return 1;
+ }
+ break;
+
+ case KEY_PPAGE:
+ {
+ int i;
+ for (i = 1; i < list_length; i++)
+ element_select_prev();
+ }
+ return 1;
+
+ case KEY_NPAGE:
+ {
+ int i;
+ for (i = 1; i < list_length; i++)
+ element_select_next();
+ }
+ return 1;
+
+ case KEY_DOWN:
+ element_select_next();
+ return 1;
+
+ case KEY_UP:
+ element_select_prev();
+ return 1;
+
+ case KEY_LEFT:
+ attr_select_prev();
+ return 1;
+
+ case KEY_RIGHT:
+ attr_select_next();
+ return 1;
+
+ case ']':
+ group_select_next();
+ return 1;
+
+ case '[':
+ group_select_prev();
+ return 1;
+
+ case '<':
+ c_ngraph--;
+ if (c_ngraph <= 1)
+ c_ngraph = 1;
+ return 1;
+
+ case '>':
+ c_ngraph++;
+ if (c_ngraph > 32)
+ c_ngraph = 32;
+ return 1;
+
+ case '\t':
+ history_select_next();
+ return 1;
+ }
+
+ return 0;
+}
+
+static void curses_pre(void)
+{
+ for (;;) {
+ int ch = getch();
+
+ if (ch == -1)
+ break;
+
+ if (handle_input(ch))
+ curses_draw();
+ }
+}
+
+static void print_module_help(void)
+{
+ printf(
+ "curses - Curses Output\n" \
+ "\n" \
+ " Interactive curses UI. Press '?' to see help.\n" \
+ " Author: Thomas Graf <tgraf@suug.ch>\n" \
+ "\n" \
+ " Options:\n" \
+ " fgchar=CHAR Foreground character (default: '*')\n" \
+ " bgchar=CHAR Background character (default: '.')\n" \
+ " nchar=CHAR Noise character (default: ':')\n" \
+ " uchar=CHAR Unknown character (default: '?')\n" \
+ " gheight=NUM Height of graph (default: 6)\n" \
+ " gwidth=NUM Width of graph (default: 60)\n" \
+ " ngraph=NUM Number of graphs (default: 1)\n" \
+ " nocolors Do not use colors\n" \
+ " graph Show graphical stats by default\n" \
+ " details Show detailed stats by default\n" \
+ " minlist=INT Minimum item list length\n");
+}
+
+static void curses_parse_opt(const char *type, const char *value)
+{
+ if (!strcasecmp(type, "fgchar") && value)
+ c_graph_cfg.gc_foreground = value[0];
+ else if (!strcasecmp(type, "bgchar") && value)
+ c_graph_cfg.gc_background = value[0];
+ else if (!strcasecmp(type, "nchar") && value)
+ c_graph_cfg.gc_noise = value[0];
+ else if (!strcasecmp(type, "uchar") && value)
+ c_graph_cfg.gc_unknown = value[0];
+ else if (!strcasecmp(type, "gheight") && value)
+ c_graph_cfg.gc_height = strtol(value, NULL, 0);
+ else if (!strcasecmp(type, "gwidth") && value)
+ c_graph_cfg.gc_width = strtol(value, NULL, 0);
+ else if (!strcasecmp(type, "ngraph")) {
+ c_ngraph = strtol(value, NULL, 0);
+ c_show_graph = !!c_ngraph;
+ } else if (!strcasecmp(type, "details"))
+ c_show_details = 1;
+ else if (!strcasecmp(type, "nocolors"))
+ c_use_colors = 0;
+ else if (!strcasecmp(type, "minlist") && value)
+ c_list_min = strtol(value, NULL, 0);
+ else if (!strcasecmp(type, "help")) {
+ print_module_help();
+ exit(0);
+ }
+}
+
+static struct bmon_module curses_ops = {
+ .m_name = "curses",
+ .m_flags = BMON_MODULE_DEFAULT,
+ .m_init = curses_init,
+ .m_shutdown = curses_shutdown,
+ .m_pre = curses_pre,
+ .m_do = curses_draw,
+ .m_parse_opt = curses_parse_opt,
+};
+
+static void __init do_curses_init(void)
+{
+ output_register(&curses_ops);
+}