/* htop - Meter.c (C) 2004-2011 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ #include "config.h" // IWYU pragma: keep #include "Meter.h" #include #include #include #include #include "CRT.h" #include "Macros.h" #include "Object.h" #include "ProvideCurses.h" #include "RichString.h" #include "Row.h" #include "Settings.h" #include "XUtils.h" #define GRAPH_HEIGHT 4 /* Unit: rows (lines) */ const MeterClass Meter_class = { .super = { .extends = Class(Object) } }; Meter* Meter_new(const Machine* host, unsigned int param, const MeterClass* type) { Meter* this = xCalloc(1, sizeof(Meter)); Object_setClass(this, type); this->h = 1; this->param = param; this->host = host; this->curItems = type->maxItems; this->curAttributes = NULL; this->values = type->maxItems ? xCalloc(type->maxItems, sizeof(double)) : NULL; this->total = type->total; this->caption = xStrdup(type->caption); if (Meter_initFn(this)) { Meter_init(this); } Meter_setMode(this, type->defaultMode); return this; } /* Converts 'value' in kibibytes into a human readable string. Example output strings: "0K", "1023K", "98.7M" and "1.23G" */ int Meter_humanUnit(char* buffer, double value, size_t size) { size_t i = 0; assert(value >= 0.0); while (value >= ONE_K) { if (i >= ARRAYSIZE(unitPrefixes) - 1) { if (value > 9999.0) { return xSnprintf(buffer, size, "inf"); } break; } value /= ONE_K; ++i; } int precision = 0; if (i > 0) { // Fraction digits for mebibytes and above precision = value <= 99.9 ? (value <= 9.99 ? 2 : 1) : 0; // Round up if 'value' is in range (99.9, 100) or (9.99, 10) if (precision < 2) { double limit = precision == 1 ? 10.0 : 100.0; if (value < limit) { value = limit; } } } return xSnprintf(buffer, size, "%.*f%c", precision, value, unitPrefixes[i]); } void Meter_delete(Object* cast) { if (!cast) return; Meter* this = (Meter*) cast; if (Meter_doneFn(this)) { Meter_done(this); } free(this->drawData.values); free(this->caption); free(this->values); free(this); } void Meter_setCaption(Meter* this, const char* caption) { free_and_xStrdup(&this->caption, caption); } static inline void Meter_displayBuffer(const Meter* this, RichString* out) { if (Object_displayFn(this)) { Object_display(this, out); } else { RichString_writeWide(out, CRT_colors[Meter_attributes(this)[0]], this->txtBuffer); } } void Meter_setMode(Meter* this, int modeIndex) { if (modeIndex > 0 && modeIndex == this->mode) { return; } if (!modeIndex) { modeIndex = 1; } assert(modeIndex < LAST_METERMODE); if (Meter_defaultMode(this) == CUSTOM_METERMODE) { this->draw = Meter_drawFn(this); if (Meter_updateModeFn(this)) { Meter_updateMode(this, modeIndex); } } else { assert(modeIndex >= 1); free(this->drawData.values); this->drawData.values = NULL; this->drawData.nValues = 0; const MeterMode* mode = Meter_modes[modeIndex]; this->draw = mode->draw; this->h = mode->h; } this->mode = modeIndex; } ListItem* Meter_toListItem(const Meter* this, bool moving) { char mode[20]; if (this->mode) { xSnprintf(mode, sizeof(mode), " [%s]", Meter_modes[this->mode]->uiName); } else { mode[0] = '\0'; } char name[32]; if (Meter_getUiNameFn(this)) Meter_getUiName(this, name, sizeof(name)); else xSnprintf(name, sizeof(name), "%s", Meter_uiName(this)); char buffer[50]; xSnprintf(buffer, sizeof(buffer), "%s%s", name, mode); ListItem* li = ListItem_new(buffer, 0); li->moving = moving; return li; } /* ---------- TextMeterMode ---------- */ static void TextMeterMode_draw(Meter* this, int x, int y, int w) { const char* caption = Meter_getCaption(this); attrset(CRT_colors[METER_TEXT]); mvaddnstr(y, x, caption, w); attrset(CRT_colors[RESET_COLOR]); int captionLen = strlen(caption); x += captionLen; w -= captionLen; if (w <= 0) return; RichString_begin(out); Meter_displayBuffer(this, &out); RichString_printoffnVal(out, y, x, 0, w); RichString_delete(&out); } /* ---------- BarMeterMode ---------- */ static const char BarMeterMode_characters[] = "|#*@$%&."; static void BarMeterMode_draw(Meter* this, int x, int y, int w) { const char* caption = Meter_getCaption(this); attrset(CRT_colors[METER_TEXT]); int captionLen = 3; mvaddnstr(y, x, caption, captionLen); x += captionLen; w -= captionLen; attrset(CRT_colors[BAR_BORDER]); mvaddch(y, x, '['); w--; mvaddch(y, x + MAXIMUM(w, 0), ']'); w--; attrset(CRT_colors[RESET_COLOR]); x++; if (w < 1) return; // The text in the bar is right aligned; // Pad with maximal spaces and then calculate needed starting position offset RichString_begin(bar); RichString_appendChr(&bar, 0, ' ', w); RichString_appendWide(&bar, 0, this->txtBuffer); int startPos = RichString_sizeVal(bar) - w; if (startPos > w) { // Text is too large for bar // Truncate meter text at a space character for (int pos = 2 * w; pos > w; pos--) { if (RichString_getCharVal(bar, pos) == ' ') { while (pos > w && RichString_getCharVal(bar, pos - 1) == ' ') pos--; startPos = pos - w; break; } } // If still too large, print the start not the end startPos = MINIMUM(startPos, w); } assert(startPos >= 0); assert(startPos <= w); assert(startPos + w <= RichString_sizeVal(bar)); int blockSizes[10]; // First draw in the bar[] buffer... int offset = 0; for (uint8_t i = 0; i < this->curItems; i++) { double value = this->values[i]; if (isPositive(value) && this->total > 0.0) { value = MINIMUM(value, this->total); blockSizes[i] = ceil((value / this->total) * w); } else { blockSizes[i] = 0; } int nextOffset = offset + blockSizes[i]; // (Control against invalid values) nextOffset = CLAMP(nextOffset, 0, w); for (int j = offset; j < nextOffset; j++) if (RichString_getCharVal(bar, startPos + j) == ' ') { if (CRT_colorScheme == COLORSCHEME_MONOCHROME) { assert(i < strlen(BarMeterMode_characters)); RichString_setChar(&bar, startPos + j, BarMeterMode_characters[i]); } else { RichString_setChar(&bar, startPos + j, '|'); } } offset = nextOffset; } // ...then print the buffer. offset = 0; for (uint8_t i = 0; i < this->curItems; i++) { int attr = this->curAttributes ? this->curAttributes[i] : Meter_attributes(this)[i]; RichString_setAttrn(&bar, CRT_colors[attr], startPos + offset, blockSizes[i]); RichString_printoffnVal(bar, y, x + offset, startPos + offset, MINIMUM(blockSizes[i], w - offset)); offset += blockSizes[i]; offset = CLAMP(offset, 0, w); } if (offset < w) { RichString_setAttrn(&bar, CRT_colors[BAR_SHADOW], startPos + offset, w - offset); RichString_printoffnVal(bar, y, x + offset, startPos + offset, w - offset); } RichString_delete(&bar); move(y, x + w + 1); attrset(CRT_colors[RESET_COLOR]); } /* ---------- GraphMeterMode ---------- */ #ifdef HAVE_LIBNCURSESW #define PIXPERROW_UTF8 4 static const char* const GraphMeterMode_dotsUtf8[] = { /*00*/" ", /*01*/"⢀", /*02*/"⢠", /*03*/"⢰", /*04*/ "⢸", /*10*/"⡀", /*11*/"⣀", /*12*/"⣠", /*13*/"⣰", /*14*/ "⣸", /*20*/"⡄", /*21*/"⣄", /*22*/"⣤", /*23*/"⣴", /*24*/ "⣼", /*30*/"⡆", /*31*/"⣆", /*32*/"⣦", /*33*/"⣶", /*34*/ "⣾", /*40*/"⡇", /*41*/"⣇", /*42*/"⣧", /*43*/"⣷", /*44*/ "⣿" }; #endif #define PIXPERROW_ASCII 2 static const char* const GraphMeterMode_dotsAscii[] = { /*00*/" ", /*01*/".", /*02*/":", /*10*/".", /*11*/".", /*12*/":", /*20*/":", /*21*/":", /*22*/":" }; static void GraphMeterMode_draw(Meter* this, int x, int y, int w) { const char* caption = Meter_getCaption(this); attrset(CRT_colors[METER_TEXT]); const int captionLen = 3; mvaddnstr(y, x, caption, captionLen); x += captionLen; w -= captionLen; GraphData* data = &this->drawData; assert(data->nValues / 2 <= INT_MAX); if (w > (int)(data->nValues / 2) && MAX_METER_GRAPHDATA_VALUES > data->nValues) { size_t oldNValues = data->nValues; data->nValues = MAXIMUM(oldNValues + oldNValues / 2, (size_t)w * 2); data->nValues = MINIMUM(data->nValues, MAX_METER_GRAPHDATA_VALUES); data->values = xReallocArray(data->values, data->nValues, sizeof(*data->values)); memmove(data->values + (data->nValues - oldNValues), data->values, oldNValues * sizeof(*data->values)); memset(data->values, 0, (data->nValues - oldNValues) * sizeof(*data->values)); } const size_t nValues = data->nValues; if (nValues < 1) return; const Machine* host = this->host; if (!timercmp(&host->realtime, &(data->time), <)) { int globalDelay = host->settings->delay; struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay % 10) * 100000L }; timeradd(&host->realtime, &delay, &(data->time)); memmove(&data->values[0], &data->values[1], (nValues - 1) * sizeof(*data->values)); data->values[nValues - 1] = sumPositiveValues(this->values, this->curItems); } if (w <= 0) return; if ((size_t)w > nValues / 2) { x += w - nValues / 2; w = nValues / 2; } const char* const* GraphMeterMode_dots; int GraphMeterMode_pixPerRow; #ifdef HAVE_LIBNCURSESW if (CRT_utf8) { GraphMeterMode_dots = GraphMeterMode_dotsUtf8; GraphMeterMode_pixPerRow = PIXPERROW_UTF8; } else #endif { GraphMeterMode_dots = GraphMeterMode_dotsAscii; GraphMeterMode_pixPerRow = PIXPERROW_ASCII; } size_t i = nValues - (size_t)w * 2; for (int col = 0; i < nValues - 1; i += 2, col++) { int pix = GraphMeterMode_pixPerRow * GRAPH_HEIGHT; double total = MAXIMUM(this->total, 1); int v1 = CLAMP((int) lround(data->values[i] / total * pix), 1, pix); int v2 = CLAMP((int) lround(data->values[i + 1] / total * pix), 1, pix); int colorIdx = GRAPH_1; for (int line = 0; line < GRAPH_HEIGHT; line++) { int line1 = CLAMP(v1 - (GraphMeterMode_pixPerRow * (GRAPH_HEIGHT - 1 - line)), 0, GraphMeterMode_pixPerRow); int line2 = CLAMP(v2 - (GraphMeterMode_pixPerRow * (GRAPH_HEIGHT - 1 - line)), 0, GraphMeterMode_pixPerRow); attrset(CRT_colors[colorIdx]); mvaddstr(y + line, x + col, GraphMeterMode_dots[line1 * (GraphMeterMode_pixPerRow + 1) + line2]); colorIdx = GRAPH_2; } } attrset(CRT_colors[RESET_COLOR]); } /* ---------- LEDMeterMode ---------- */ static const char* const LEDMeterMode_digitsAscii[] = { " __ ", " ", " __ ", " __ ", " ", " __ ", " __ ", " __ ", " __ ", " __ ", "| |", " |", " __|", " __|", "|__|", "|__ ", "|__ ", " |", "|__|", "|__|", "|__|", " |", "|__ ", " __|", " |", " __|", "|__|", " |", "|__|", " __|" }; #ifdef HAVE_LIBNCURSESW static const char* const LEDMeterMode_digitsUtf8[] = { "┌──┐", " ┐ ", "╶──┐", "╶──┐", "╷ ╷", "┌──╴", "┌──╴", "╶──┐", "┌──┐", "┌──┐", "│ │", " │ ", "┌──┘", " ──┤", "└──┤", "└──┐", "├──┐", " │", "├──┤", "└──┤", "└──┘", " ╵ ", "└──╴", "╶──┘", " ╵", "╶──┘", "└──┘", " ╵", "└──┘", "╶──┘" }; #endif static const char* const* LEDMeterMode_digits; static void LEDMeterMode_drawDigit(int x, int y, int n) { for (int i = 0; i < 3; i++) mvaddstr(y + i, x, LEDMeterMode_digits[i * 10 + n]); } static void LEDMeterMode_draw(Meter* this, int x, int y, int w) { #ifdef HAVE_LIBNCURSESW if (CRT_utf8) LEDMeterMode_digits = LEDMeterMode_digitsUtf8; else #endif LEDMeterMode_digits = LEDMeterMode_digitsAscii; RichString_begin(out); Meter_displayBuffer(this, &out); int yText = #ifdef HAVE_LIBNCURSESW CRT_utf8 ? y + 1 : #endif y + 2; attrset(CRT_colors[LED_COLOR]); const char* caption = Meter_getCaption(this); mvaddstr(yText, x, caption); int xx = x + strlen(caption); int len = RichString_sizeVal(out); for (int i = 0; i < len; i++) { int c = RichString_getCharVal(out, i); if (c >= '0' && c <= '9') { if (xx - x + 4 > w) break; LEDMeterMode_drawDigit(xx, y, c - '0'); xx += 4; } else { if (xx - x + 1 > w) break; #ifdef HAVE_LIBNCURSESW const cchar_t wc = { .chars = { c, '\0' }, .attr = 0 }; /* use LED_COLOR from attrset() */ mvadd_wch(yText, xx, &wc); #else mvaddch(yText, xx, c); #endif xx += 1; } } attrset(CRT_colors[RESET_COLOR]); RichString_delete(&out); } static MeterMode BarMeterMode = { .uiName = "Bar", .h = 1, .draw = BarMeterMode_draw, }; static MeterMode TextMeterMode = { .uiName = "Text", .h = 1, .draw = TextMeterMode_draw, }; static MeterMode GraphMeterMode = { .uiName = "Graph", .h = GRAPH_HEIGHT, .draw = GraphMeterMode_draw, }; static MeterMode LEDMeterMode = { .uiName = "LED", .h = 3, .draw = LEDMeterMode_draw, }; const MeterMode* const Meter_modes[] = { NULL, &BarMeterMode, &TextMeterMode, &GraphMeterMode, &LEDMeterMode, NULL }; /* Blank meter */ static void BlankMeter_updateValues(Meter* this) { this->txtBuffer[0] = '\0'; } static void BlankMeter_display(ATTR_UNUSED const Object* cast, ATTR_UNUSED RichString* out) { } static const int BlankMeter_attributes[] = { DEFAULT_COLOR }; const MeterClass BlankMeter_class = { .super = { .extends = Class(Meter), .delete = Meter_delete, .display = BlankMeter_display, }, .updateValues = BlankMeter_updateValues, .defaultMode = TEXT_METERMODE, .maxItems = 0, .total = 100.0, .attributes = BlankMeter_attributes, .name = "Blank", .uiName = "Blank", .caption = "" };