/* * Copyright © 2009 CNRS * Copyright © 2009-2018 Inria. All rights reserved. * Copyright © 2009-2012 Université Bordeaux * Copyright © 2009-2011 Cisco Systems, Inc. All rights reserved. * See COPYING in top-level directory. */ /* * Pretty text output */ #include #include #include #ifdef HAVE_NL_LANGINFO #include #endif /* HAVE_NL_LANGINFO */ #ifdef HAVE_PUTWC #include #endif /* HAVE_PUTWC */ #ifdef HWLOC_HAVE_LIBTERMCAP #ifdef HWLOC_USE_NCURSES # include #else # include #endif #include #endif /* HWLOC_HAVE_LIBTERMCAP */ #include "lstopo.h" /* Uses unicode bars if available */ #ifdef HAVE_PUTWC typedef wchar_t character; #define PRIchar "lc" #define putcharacter(c,f) putwc(c,f) #else /* HAVE_PUTWC */ typedef unsigned char character; #define PRIchar "c" #define putcharacter(c,f) putc(c,f) #endif /* HAVE_PUTWC */ #ifdef HWLOC_HAVE_LIBTERMCAP static int myputchar(int c) { return putcharacter(c, stdout); } #endif /* HWLOC_HAVE_LIBTERMCAP */ /* Off-screen rendering buffer */ struct cell { character c; #ifdef HWLOC_HAVE_LIBTERMCAP int fr, fg, fb; int br, bg, bb; #endif /* HWLOC_HAVE_LIBTERMCAP */ }; struct lstopo_ascii_output { struct lstopo_output loutput; /* must be at the beginning */ struct cell **cells; int width; int height; int utf8; int drawing; }; static struct draw_methods ascii_draw_methods; /* Allocate the off-screen buffer */ static void ascii_init(void *_output) { struct lstopo_ascii_output *disp = _output; unsigned width, height; unsigned j, i; /* compute the required size */ disp->drawing = 0; output_draw(&disp->loutput); width = disp->width; height = disp->height; disp->drawing = 1; /* terminals usually have narrow characters, so let's make them wider */ disp->cells = malloc(height * sizeof(*disp->cells)); for (j = 0; j < height; j++) { disp->cells[j] = calloc(width, sizeof(**disp->cells)); for (i = 0; i < width; i++) disp->cells[j][i].c = ' '; } #ifdef HAVE_NL_LANGINFO disp->utf8 = !strcmp(nl_langinfo(CODESET), "UTF-8"); #endif /* HAVE_NL_LANGINFO */ } #ifdef HWLOC_HAVE_LIBTERMCAP /* Standard terminfo strings */ static char *initc = NULL, *initp = NULL; /* Set text color to bright white or black according to the background */ static int set_textcolor(int rr, int gg, int bb) { if (!initc && !initp && rr + gg + bb < 2) { if (enter_bold_mode) tputs(enter_bold_mode, 1, myputchar); return 7; } else { if (exit_attribute_mode) tputs(exit_attribute_mode, 1, myputchar); return 0; } } static void set_color(int fr, int fg, int fb, int br, int bg, int bb) { char *toput; int color, textcolor; if (initc || initp) { /* Can set rgb color, easy */ textcolor = rgb_to_color(fr, fg, fb) + 16; color = rgb_to_color(br, bg, bb) + 16; } else { /* Magic trigger: it seems to separate colors quite well */ int brr = br >= 0xe0; int bgg = bg >= 0xe0; int bbb = bb >= 0xe0; if (set_a_background) /* ANSI colors */ color = (brr << 0) | (bgg << 1) | (bbb << 2); else /* Legacy colors */ color = (brr << 2) | (bgg << 1) | (bbb << 0); textcolor = set_textcolor(brr, bgg, bbb); } /* And now output magic string to TTY */ if (set_a_foreground) { /* foreground */ if ((toput = tparm(set_a_foreground, textcolor, 0, 0, 0, 0, 0, 0, 0, 0))) tputs(toput, 1, myputchar); /* background */ if ((toput = tparm(set_a_background, color, 0, 0, 0, 0, 0, 0, 0, 0))) tputs(toput, 1, myputchar); } else if (set_foreground) { /* foreground */ if ((toput = tparm(set_foreground, textcolor, 0, 0, 0, 0, 0, 0, 0, 0))) tputs(toput, 1, myputchar); /* background */ if ((toput = tparm(set_background, color, 0, 0, 0, 0, 0, 0, 0, 0))) tputs(toput, 1, myputchar); } else if (set_color_pair) { /* pair */ if ((toput = tparm(set_color_pair, color, 0, 0, 0, 0, 0, 0, 0, 0))) tputs(toput, 1, myputchar); } } #endif /* HWLOC_HAVE_LIBTERMCAP */ /* We we can, allocate rgb colors */ static void ascii_declare_color(void *output __hwloc_attribute_unused, int r __hwloc_attribute_unused, int g __hwloc_attribute_unused, int b __hwloc_attribute_unused) { struct lstopo_ascii_output *disp = output; #ifdef HWLOC_HAVE_LIBTERMCAP int color, rr, gg, bb; char *toput; #endif if (!disp->drawing) return; #ifdef HWLOC_HAVE_LIBTERMCAP color = declare_color(r, g, b); /* Yes, values seem to range from 0 to 1000 inclusive */ rr = (r * 1001) / 256; gg = (g * 1001) / 256; bb = (b * 1001) / 256; if (initc) { if ((toput = tparm(initc, color + 16, rr, gg, bb, 0, 0, 0, 0, 0))) tputs(toput, 1, myputchar); } else if (initp) { if ((toput = tparm(initp, color + 16, 0, 0, 0, rr, gg, bb, 0, 0))) tputs(toput, 1, myputchar); } #endif /* HWLOC_HAVE_LIBTERMCAP */ } /* output text, erasing any previous content */ static void put(struct lstopo_ascii_output *disp, int x, int y, character c, int fr __hwloc_attribute_unused, int fg __hwloc_attribute_unused, int fb __hwloc_attribute_unused, int br __hwloc_attribute_unused, int bg __hwloc_attribute_unused, int bb __hwloc_attribute_unused) { if (x >= disp->width || y >= disp->height) { /* fprintf(stderr, "%"PRIchar" overflowed to (%d,%d)\n", c, x, y); */ return; } disp->cells[y][x].c = c; #ifdef HWLOC_HAVE_LIBTERMCAP if (fr != -1) { disp->cells[y][x].fr = fr; disp->cells[y][x].fg = fg; disp->cells[y][x].fb = fb; } if (br != -1) { disp->cells[y][x].br = br; disp->cells[y][x].bg = bg; disp->cells[y][x].bb = bb; } #endif /* HWLOC_HAVE_LIBTERMCAP */ } /* Where bars of a character go to */ enum { up = (1<<0), down = (1<<1), left = (1<<2), right = (1<<3) }; /* Convert a bar character into its directions */ static int to_directions(struct lstopo_ascii_output *disp, character c) { #ifdef HAVE_PUTWC if (disp->utf8) { switch (c) { case L'\x250c': return down|right; case L'\x2510': return down|left; case L'\x2514': return up|right; case L'\x2518': return up|left; case L'\x2500': return left|right; case L'\x2502': return down|up; case L'\x2577': return down; case L'\x2575': return up; case L'\x2576': return right; case L'\x2574': return left; case L'\x251c': return down|up|right; case L'\x2524': return down|up|left; case L'\x252c': return down|left|right; case L'\x2534': return up|left|right; case L'\x253c': return down|up|left|right; default: return 0; } } else #endif /* HAVE_PUTWC */ { switch (c) { case L'-': return left|right; case L'|': return down|up; case L'/': case L'\\': case L'+': return down|up|left|right; default: return 0; } } } /* Produce a bar character given the wanted directions */ static character from_directions(struct lstopo_ascii_output *disp, int direction) { #ifdef HAVE_PUTWC if (disp->utf8) { switch (direction) { case down|right: return L'\x250c'; case down|left: return L'\x2510'; case up|right: return L'\x2514'; case up|left: return L'\x2518'; case left|right: return L'\x2500'; case down|up: return L'\x2502'; case down: return L'\x2577'; case up: return L'\x2575'; case right: return L'\x2576'; case left: return L'\x2574'; case down|up|right: return L'\x251c'; case down|up|left: return L'\x2524'; case down|left|right: return L'\x252c'; case up|left|right: return L'\x2534'; case down|up|left|right: return L'\x253c'; default: return L' '; }; } else #endif /* HAVE_PUTWC */ { switch (direction) { case down|right: return '/'; case down|left: return '\\'; case up|right: return '\\'; case up|left: return '/'; case left|right: return '-'; case down|up: return '|'; case down: return '|'; case up: return '|'; case right: return '-'; case left: return '-'; case down|up|right: return '+'; case down|up|left: return '+'; case down|left|right: return '+'; case up|left|right: return '+'; case down|up|left|right: return '+'; default: return ' '; }; } } /* output bars, merging with existing bars: `andnot' are removed, `or' are added */ static void merge(struct lstopo_ascii_output *disp, int x, int y, int or, int andnot, int r, int g, int b) { character current; int directions; if (x >= disp->width || y >= disp->height) { /* fprintf(stderr, "|%x &~%x overflowed to (%d,%d)\n", or, andnot, x, y); */ return; } current = disp->cells[y][x].c; directions = (to_directions(disp, current) & ~andnot) | or; put(disp, x, y, from_directions(disp, directions), -1, -1, -1, r, g, b); } /* Now we can implement the standard drawing helpers */ static void ascii_box(void *output, int r, int g, int b, unsigned depth __hwloc_attribute_unused, unsigned x1, unsigned width, unsigned y1, unsigned height) { struct lstopo_ascii_output *disp = output; unsigned i, j; unsigned x2, y2; x1 /= (gridsize/2); width /= (gridsize/2); y1 /= gridsize; height /= gridsize; x2 = x1 + width - 1; y2 = y1 + height - 1; if (!disp->drawing) { if ((int)x1 >= disp->width) disp->width = x1+1; if ((int)x2 >= disp->width) disp->width = x2+1; if ((int)y1 >= disp->height) disp->height = y1+1; if ((int)y2 >= disp->height) disp->height = y2+1; return; } /* Corners */ merge(disp, x1, y1, down|right, 0, r, g, b); merge(disp, x2, y1, down|left, 0, r, g, b); merge(disp, x1, y2, up|right, 0, r, g, b); merge(disp, x2, y2, up|left, 0, r, g, b); for (i = 1; i < width - 1; i++) { /* upper line */ merge(disp, x1 + i, y1, left|right, down, r, g, b); /* lower line */ merge(disp, x1 + i, y2, left|right, up, r, g, b); } for (j = 1; j < height - 1; j++) { /* left line */ merge(disp, x1, y1 + j, up|down, right, r, g, b); /* right line */ merge(disp, x2, y1 + j, up|down, left, r, g, b); } for (j = y1 + 1; j < y2; j++) { for (i = x1 + 1; i < x2; i++) { put(disp, i, j, ' ', -1, -1, -1, r, g, b); } } } static void ascii_line(void *output, int r __hwloc_attribute_unused, int g __hwloc_attribute_unused, int b __hwloc_attribute_unused, unsigned depth __hwloc_attribute_unused, unsigned x1, unsigned y1, unsigned x2, unsigned y2) { struct lstopo_ascii_output *disp = output; unsigned i, j, z; x1 /= (gridsize/2); y1 /= gridsize; x2 /= (gridsize/2); y2 /= gridsize; /* Canonicalize coordinates */ if (x1 > x2) { z = x1; x1 = x2; x2 = z; } if (y1 > y2) { z = y1; y1 = y2; y2 = z; } if (!disp->drawing) { if ((int)x2 >= disp->width) disp->width = x2+1; if ((int)y2 >= disp->height) disp->height = y2+1; return; } /* vertical/horizontal should be enough, but should mix with existing * characters for better output ! */ if (x1 == x2) { /* Vertical */ if (y1 == y2) { /* Hu ?! That's a point, let's do nothing... */ } else { /* top */ merge(disp, x1, y1, down, 0, -1, -1, -1); /* bottom */ merge(disp, x1, y2, up, 0, -1, -1, -1); } for (j = y1 + 1; j < y2; j++) merge(disp, x1, j, down|up, 0, -1, -1, -1); } else if (y1 == y2) { /* Horizontal */ /* left */ merge(disp, x1, y1, right, 0, -1, -1, -1); /* right */ merge(disp, x2, y1, left, 0, -1, -1, -1); for (i = x1 + 1; i < x2; i++) merge(disp, i, y1, left|right, 0, -1, -1, -1); } else { /* Unsupported, sorry */ } } static void ascii_text(void *output, int r, int g, int b, unsigned depth __hwloc_attribute_unused, unsigned x, unsigned y, const char *text) { struct lstopo_ascii_output *disp = output; if (!disp->drawing) return; x /= (gridsize/2); y /= gridsize; #if defined(HAVE_PUTWC) && !defined(__MINGW32__) && !defined(_MSC_VER) { size_t len = strlen(text) + 1; wchar_t *wbuf = malloc(len * sizeof(wchar_t)), *wtext; swprintf(wbuf, len, L"%s", text); for (wtext = wbuf ; *wtext; wtext++) put(disp, x++, y, *wtext, r, g, b, -1, -1, -1); free(wbuf); } #else for ( ; *text; text++) put(disp, x++, y, *text, r, g, b, -1, -1, -1); #endif } static void ascii_textsize(void *output __hwloc_attribute_unused, const char *text __hwloc_attribute_unused, unsigned textlength, unsigned *width) { *width = textlength*(gridsize/2); } static struct draw_methods ascii_draw_methods = { ascii_init, ascii_declare_color, ascii_box, ascii_line, ascii_text, ascii_textsize, }; void output_ascii(struct lstopo_output *loutput, const char *filename) { FILE *output; struct lstopo_ascii_output _disp, *disp = &_disp; int i, j; int lfr, lfg, lfb; /* Last foreground color */ int lbr, lbg, lbb; /* Last background color */ #ifdef HWLOC_HAVE_LIBTERMCAP int term = 0; char *tmp; #endif output = open_output(filename, loutput->overwrite); if (!output) { fprintf(stderr, "Failed to open %s for writing (%s)\n", filename, strerror(errno)); return; } if (gridsize <= 1) { /* we divide by gridsize or gridsize/2 in the code */ fprintf(stderr, "ASCII backend requires gridsize > 1\n"); return; } #ifdef HWLOC_HAVE_LIBTERMCAP /* If we are outputing to a tty, use colors */ if (output == stdout && isatty(STDOUT_FILENO)) { term = !setupterm(NULL, STDOUT_FILENO, NULL); if (term) { /* reset colors */ if (orig_colors) tputs(orig_colors, 1, myputchar); /* Get terminfo(5) strings */ initp = initialize_pair; if (max_pairs <= 16 || !initp || !set_color_pair) { /* Can't use max_pairs to define our own colors */ initp = NULL; if (max_colors > 16) if (can_change) initc = initialize_color; } /* Prevent a trivial compiler warning because the param of tgetflag is (char*), not (const char*). */ tmp = strdup("lhs"); if (tgetflag(tmp)) { /* Sorry, I'm lazy to convert colors and I don't know any terminal * using LHS anyway */ initc = initp = 0; } free(tmp); } } #endif /* HWLOC_HAVE_LIBTERMCAP */ memcpy(&disp->loutput, loutput, sizeof(*loutput)); disp->loutput.methods = &ascii_draw_methods; disp->width = 0; disp->height = 0; output_draw_start(&disp->loutput); output_draw(&disp->loutput); lfr = lfg = lfb = -1; lbr = lbg = lbb = -1; for (j = 0; j < disp->height; j++) { for (i = 0; i < disp->width; i++) { #ifdef HWLOC_HAVE_LIBTERMCAP if (term) { /* TTY output, use colors */ int fr = disp->cells[j][i].fr; int fg = disp->cells[j][i].fg; int fb = disp->cells[j][i].fb; int br = disp->cells[j][i].br; int bg = disp->cells[j][i].bg; int bb = disp->cells[j][i].bb; /* Avoid too much work for the TTY */ if (fr != lfr || fg != lfg || fb != lfb || br != lbr || bg != lbg || bb != lbb) { set_color(fr, fg, fb, br, bg, bb); lfr = fr; lfg = fg; lfb = fb; lbr = br; lbg = bg; lbb = bb; } } #endif /* HWLOC_HAVE_LIBTERMCAP */ putcharacter(disp->cells[j][i].c, output); } #ifdef HWLOC_HAVE_LIBTERMCAP /* Keep the rest of the line as default */ if (term && orig_pair) { lfr = lfg = lfb = -1; lbr = lbg = lbb = -1; tputs(orig_pair, 1, myputchar); } #endif /* HWLOC_HAVE_LIBTERMCAP */ putcharacter('\n', output); } for (j = 0; j < disp->height; j++) free(disp->cells[j]); free(disp->cells); if (output != stdout) fclose(output); }