ohmu
·
2026-10-15
1#include "fastfetch.h"
2#include "common/debug.h"
3#include "common/percent.h"
4#include "common/color.h"
5#include "common/option.h"
6#include "common/jsonconfig.h"
7#include "common/textModifier.h"
8#include "common/strutil.h"
9
10static void appendOutputColor(FFstrbuf* buffer, const FFModuleArgs* module) {
11 if (module->outputColor.length) {
12 ffStrbufAppendF(buffer, "\e[%sm", module->outputColor.chars);
13 } else if (instance.config.display.colorOutput.length) {
14 ffStrbufAppendF(buffer, "\e[%sm", instance.config.display.colorOutput.chars);
15 }
16}
17
18const char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFlags* result) {
19 if (yyjson_is_uint(jsonVal)) {
20 *result = (FFPercentageTypeFlags) yyjson_get_uint(jsonVal);
21 return nullptr;
22 }
23 if (yyjson_is_arr(jsonVal)) {
24 FFPercentageTypeFlags flags = 0;
25
26 yyjson_val* item;
27 size_t idx, max;
28 yyjson_arr_foreach (jsonVal, idx, max, item) {
29 const char* flag = yyjson_get_str(item);
30 if (!flag) {
31 FF_DEBUG("A percent.type array entry is of JSON type %s, not a string",
32 yyjson_get_type_desc(item));
33 return "Error: percent.type: invalid flag string";
34 }
35 if (ffStrEqualsIgnCase(flag, "num")) {
36 flags |= FF_PERCENTAGE_TYPE_NUM_BIT;
37 } else if (ffStrEqualsIgnCase(flag, "bar")) {
38 flags |= FF_PERCENTAGE_TYPE_BAR_BIT;
39 } else if (ffStrEqualsIgnCase(flag, "hide-others")) {
40 flags |= FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT;
41 } else if (ffStrEqualsIgnCase(flag, "num-color")) {
42 flags |= FF_PERCENTAGE_TYPE_NUM_COLOR_BIT;
43 } else if (ffStrEqualsIgnCase(flag, "bar-monochrome")) {
44 flags |= FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT;
45 } else {
46 FF_DEBUG("Unknown percent.type flag \"%s\"", flag);
47 return "Error: percent.type: unknown flag string";
48 }
49 }
50
51 *result = flags;
52 return nullptr;
53 }
54
55 FF_DEBUG("percent.type is of JSON type %s", yyjson_get_type_desc(jsonVal));
56 return "Error: usage: percent.type must be a number or an array of strings";
57}
58
59void ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, const FFModuleArgs* module) {
60 uint8_t green = config.green, yellow = config.yellow;
61 assert(green <= 100 && yellow <= 100);
62
63 const FFOptionsDisplay* options = &instance.config.display;
64
65 const bool borderAsValue = options->barBorderLeftElapsed.length && options->barBorderRightElapsed.length;
66
67 if (!borderAsValue && options->barBorderLeft.length) {
68 if (!options->pipe && options->barColorBorder.length > 0) {
69 ffStrbufAppendF(buffer, "\e[%sm", options->barColorBorder.chars);
70 }
71 ffStrbufAppend(buffer, &options->barBorderLeft);
72 }
73
74 if (percent == -DBL_MAX) {
75 // Use total color for simplification
76 if (!options->pipe && options->barColorTotal.length > 0) {
77 ffStrbufAppendS(buffer, "\e[" FF_COLOR_FG_LIGHT_BLACK "m");
78 }
79
80 for (uint8_t i = 0; i < options->barWidth; ++i) {
81 ffStrbufAppend(buffer, borderAsValue && i == 0 ? &options->barBorderLeft : borderAsValue && i == options->barWidth - 1 ? &options->barBorderRight
82 : &options->barCharTotal);
83 }
84 } else {
85 const char* colorGreen = options->percentColorGreen.chars;
86 const char* colorYellow = options->percentColorYellow.chars;
87 const char* colorRed = options->percentColorRed.chars;
88
89 FFPercentageTypeFlags percentType = config.type == 0 ? options->percentType : config.type;
90
91 uint8_t blocksPercent = 0;
92 if (percent > 0) {
93 double blocks = percent / 100.0 * options->barWidth + 0.5;
94 blocksPercent = (uint8_t) (blocks >= options->barWidth ? options->barWidth : blocks);
95 }
96
97 bool autoColorElapsed = ffStrbufIgnCaseEqualS(&options->barColorElapsed, "auto");
98
99 bool monochrome = (percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT) || !autoColorElapsed;
100 if (!options->pipe && options->barColorElapsed.length > 0 && monochrome) {
101 const char* color = nullptr;
102 if (!autoColorElapsed) {
103 color = options->barColorElapsed.chars;
104 } else if (green <= yellow) {
105 if (percent < green) {
106 color = colorGreen;
107 } else if (percent < yellow) {
108 color = colorYellow;
109 } else {
110 color = colorRed;
111 }
112 } else {
113 if (percent < yellow) {
114 color = colorRed;
115 } else if (percent < green) {
116 color = colorYellow;
117 } else {
118 color = colorGreen;
119 }
120 }
121 ffStrbufAppendF(buffer, "\e[%sm", color);
122 }
123 for (uint8_t i = 0; i < blocksPercent; ++i) {
124 if (!options->pipe && options->barColorElapsed.length > 0 && !monochrome) {
125 uint32_t section1Begin = (uint32_t) ((green <= yellow ? green : yellow) / 100.0 * options->barWidth + 0.5);
126 uint32_t section2Begin = (uint32_t) ((green > yellow ? green : yellow) / 100.0 * options->barWidth + 0.5);
127 if (i == section2Begin) {
128 ffStrbufAppendF(buffer, "\e[%sm", (green > yellow ? colorGreen : colorRed));
129 } else if (i == section1Begin) {
130 ffStrbufAppendF(buffer, "\e[%sm", colorYellow);
131 } else if (i == 0) {
132 ffStrbufAppendF(buffer, "\e[%sm", (green <= yellow ? colorGreen : colorRed));
133 }
134 }
135 ffStrbufAppend(buffer, borderAsValue && i == 0 ? &options->barBorderLeftElapsed : borderAsValue && i == options->barWidth - 1 ? &options->barBorderRightElapsed
136 : &options->barCharElapsed);
137 }
138
139 if (blocksPercent < options->barWidth) {
140 if (!options->pipe && options->barColorTotal.length > 0) {
141 ffStrbufAppendF(buffer, "\e[%sm", options->barColorTotal.chars);
142 }
143 for (uint8_t i = blocksPercent; i < options->barWidth; ++i) {
144 ffStrbufAppend(buffer, borderAsValue && i == 0 ? &options->barBorderLeft : borderAsValue && i == options->barWidth - 1 ? &options->barBorderRight
145 : &options->barCharTotal);
146 }
147 }
148 }
149
150 if (!borderAsValue && options->barBorderRight.length) {
151 if (!options->pipe && options->barColorBorder.length > 0) {
152 ffStrbufAppendF(buffer, "\e[%sm", options->barColorBorder.chars);
153 }
154 ffStrbufAppend(buffer, &options->barBorderRight);
155 }
156
157 if (!options->pipe && (options->barColorElapsed.length > 0 || options->barColorTotal.length > 0 || options->barColorBorder.length > 0)) {
158 ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);
159 appendOutputColor(buffer, module);
160 }
161}
162
163void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, bool parentheses, const FFModuleArgs* module) {
164 uint8_t green = config.green, yellow = config.yellow;
165 assert(green <= 100 && yellow <= 100);
166
167 const FFOptionsDisplay* options = &instance.config.display;
168 FFPercentageTypeFlags percentType = config.type == 0 ? options->percentType : config.type;
169
170 bool colored = !!(percentType & FF_PERCENTAGE_TYPE_NUM_COLOR_BIT);
171
172 if (parentheses) {
173 ffStrbufAppendC(buffer, '(');
174 }
175
176 if (colored && !options->pipe) {
177 const char* colorGreen = options->percentColorGreen.chars;
178 const char* colorYellow = options->percentColorYellow.chars;
179 const char* colorRed = options->percentColorRed.chars;
180
181 if (percent == -DBL_MAX) {
182 ffStrbufAppendS(buffer, "\e[" FF_COLOR_FG_LIGHT_BLACK "m");
183 } else if (green <= yellow) {
184 if (percent > yellow) {
185 ffStrbufAppendF(buffer, "\e[%sm", colorRed);
186 } else if (percent > green) {
187 ffStrbufAppendF(buffer, "\e[%sm", colorYellow);
188 } else {
189 ffStrbufAppendF(buffer, "\e[%sm", colorGreen);
190 }
191 } else {
192 if (percent < yellow) {
193 ffStrbufAppendF(buffer, "\e[%sm", colorRed);
194 } else if (percent < green) {
195 ffStrbufAppendF(buffer, "\e[%sm", colorYellow);
196 } else {
197 ffStrbufAppendF(buffer, "\e[%sm", colorGreen);
198 }
199 }
200 }
201 if (percent == -DBL_MAX) {
202 ffStrbufAppendS(buffer, "-");
203 } else {
204 ffStrbufAppendF(buffer, "%*.*f%s%%", options->percentWidth, options->percentNdigits, percent, options->percentSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : "");
205 }
206
207 if (colored && !options->pipe) {
208 ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);
209 appendOutputColor(buffer, module);
210 }
211
212 if (parentheses) {
213 ffStrbufAppendC(buffer, ')');
214 }
215}
216
217bool ffPercentParseCommandOptions(const char* key, const char* subkey, const char* value, FFPercentageModuleConfig* config) {
218 if (!ffStrStartsWithIgnCase(subkey, "percent-")) {
219 return false;
220 }
221
222 subkey += strlen("percent-");
223
224 if (ffStrEqualsIgnCase(subkey, "green")) {
225 uint32_t num = ffOptionParseUInt32(key, value);
226 if (num > 100) {
227 fprintf(stderr, "Error: usage: %s must be between 0 and 100\n", key);
228 exit(480);
229 }
230 config->green = (uint8_t) num;
231 return true;
232 }
233
234 if (ffStrEqualsIgnCase(subkey, "yellow")) {
235 uint32_t num = ffOptionParseUInt32(key, value);
236 if (num > 100) {
237 fprintf(stderr, "Error: usage: %s must be between 0 and 100\n", key);
238 exit(480);
239 }
240 config->yellow = (uint8_t) num;
241 return true;
242 }
243
244 if (ffStrEqualsIgnCase(subkey, "type")) {
245 config->type = (FFPercentageTypeFlags) ffOptionParseUInt32(key, value);
246 return true;
247 }
248
249 return false;
250}
251
252bool ffPercentParseJsonObject(yyjson_val* key, yyjson_val* value, FFPercentageModuleConfig* config) {
253 assert(key);
254
255 if (!unsafe_yyjson_equals_str(key, "percent")) {
256 return false;
257 }
258
259 if (!yyjson_is_obj(value)) {
260 fprintf(stderr, "Error: usage: %s must be an object\n", unsafe_yyjson_get_str(key));
261 exit(480);
262 }
263
264 yyjson_val* greenVal = yyjson_obj_get(value, "green");
265 if (greenVal) {
266 int num = yyjson_get_int(greenVal);
267 if (num < 0 || num > 100) {
268 fputs("Error: usage: percent.green must be between 0 and 100\n", stderr);
269 exit(480);
270 }
271 config->green = (uint8_t) num;
272 }
273
274 yyjson_val* yellowVal = yyjson_obj_get(value, "yellow");
275 if (yellowVal) {
276 int num = yyjson_get_int(yellowVal);
277 if (num < 0 || num > 100) {
278 fputs("Error: usage: percent.yellow must be between 0 and 100\n", stderr);
279 exit(480);
280 }
281 config->yellow = (uint8_t) num;
282 }
283
284 yyjson_val* typeVal = yyjson_obj_get(value, "type");
285 if (typeVal) {
286 const char* error = ffPercentParseTypeJsonConfig(typeVal, &config->type);
287 if (error) {
288 fputs(error, stderr);
289 exit(480);
290 }
291 }
292
293 return true;
294}
295
296[[gnu::cold]]
297void ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config) {
298 yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, module, "percent");
299 yyjson_mut_obj_add_uint(doc, percent, "green", config.green);
300 yyjson_mut_obj_add_uint(doc, percent, "yellow", config.yellow);
301 yyjson_mut_obj_add_uint(doc, percent, "type", config.type);
302}