master ohmu/fastfetch-openbsd / src / modules / battery / battery.c
ohmu  ·  2026-10-15
  1#include "common/printing.h"
  2#include "common/jsonconfig.h"
  3#include "common/percent.h"
  4#include "common/duration.h"
  5#include "common/temps.h"
  6#include "common/strutil.h"
  7#include "detection/battery/battery.h"
  8#include "modules/battery/battery.h"
  9
 10static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uint8_t index) {
 11    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
 12    if (options->moduleArgs.key.length == 0) {
 13        if (result->modelName.length > 0) {
 14            ffStrbufSetF(&key, "%s (%s)", FF_MODULE_GET_DISPLAY_NAME(Battery), result->modelName.chars);
 15        } else {
 16            ffStrbufSetS(&key, FF_MODULE_GET_DISPLAY_NAME(Battery));
 17        }
 18    } else {
 19        ffStrbufClear(&key);
 20        FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {
 21                                                                           FF_ARG(index, "index"),
 22                                                                           FF_ARG(result->modelName, "model-name"),
 23                                                                           FF_ARG(options->moduleArgs.keyIcon, "icon"),
 24                                                                           FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Battery), "module-name"),
 25                                                                       }));
 26    }
 27
 28    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;
 29
 30    if (options->moduleArgs.outputFormat.length == 0) {
 31        ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);
 32
 33        FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();
 34        bool showStatus = !(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT) && result->status;
 35
 36        if (result->capacity >= 0) {
 37            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) {
 38                ffPercentAppendBar(&str, result->capacity, options->percent, &options->moduleArgs);
 39            }
 40
 41            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) {
 42                if (str.length > 0) {
 43                    ffStrbufAppendC(&str, ' ');
 44                }
 45
 46                ffPercentAppendNum(&str, result->capacity, options->percent, str.length > 0, &options->moduleArgs);
 47            }
 48
 49            if (result->timeRemaining > 0) {
 50                if (str.length > 0) {
 51                    ffStrbufAppendS(&str, " (");
 52                }
 53
 54                ffDurationAppendNum((uint32_t) result->timeRemaining, &str);
 55                ffStrbufAppendS(&str, " remaining)");
 56            }
 57        }
 58
 59        if (showStatus) {
 60            bool hasOtherInfo = str.length > 0;
 61            if (hasOtherInfo) {
 62                ffStrbufAppendS(&str, " [");
 63            }
 64            if (result->status & FF_BATTERY_STATUS_AC_CONNECTED) {
 65                ffStrbufAppendS(&str, "AC Connected, ");
 66            }
 67            if (result->status & FF_BATTERY_STATUS_USB_CONNECTED) {
 68                ffStrbufAppendS(&str, "USB Connected, ");
 69            }
 70            if (result->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) {
 71                ffStrbufAppendS(&str, "Wireless Connected, ");
 72            }
 73            if (result->status & FF_BATTERY_STATUS_CHARGING) {
 74                ffStrbufAppendS(&str, "Charging, ");
 75            }
 76            if (result->status & FF_BATTERY_STATUS_DISCHARGING) {
 77                ffStrbufAppendS(&str, "Discharging, ");
 78            }
 79            if (result->status & FF_BATTERY_STATUS_CRITICAL) {
 80                ffStrbufAppendS(&str, "Critical, ");
 81            }
 82            if (result->status & FF_BATTERY_STATUS_UNKNOWN) {
 83                ffStrbufAppendS(&str, "Unknown, ");
 84            }
 85            ffStrbufSubstrBefore(&str, str.length - 2); // Remove last ", "
 86            if (hasOtherInfo) {
 87                ffStrbufAppendC(&str, ']');
 88            }
 89        }
 90
 91        if (result->temperature != FF_BATTERY_TEMP_UNSET) {
 92            if (str.length > 0) {
 93                ffStrbufAppendS(&str, " - ");
 94            }
 95
 96            ffTempsAppendNum(result->temperature, &str, options->tempConfig, &options->moduleArgs);
 97        }
 98
 99        ffStrbufPutTo(&str, stdout);
100    } else {
101        uint32_t timeRemaining = result->timeRemaining < 0 ? 0 : (uint32_t) result->timeRemaining;
102        uint32_t seconds = timeRemaining % 60;
103        timeRemaining /= 60;
104        uint32_t minutes = timeRemaining % 60;
105        timeRemaining /= 60;
106        uint32_t hours = timeRemaining % 24;
107        timeRemaining /= 24;
108        uint32_t days = timeRemaining;
109
110        FF_STRBUF_AUTO_DESTROY capacityNum = ffStrbufCreate();
111        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) {
112            ffPercentAppendNum(&capacityNum, result->capacity, options->percent, false, &options->moduleArgs);
113        }
114        FF_STRBUF_AUTO_DESTROY capacityBar = ffStrbufCreate();
115        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) {
116            ffPercentAppendBar(&capacityBar, result->capacity, options->percent, &options->moduleArgs);
117        }
118        FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate();
119        ffTempsAppendNum(result->temperature, &tempStr, options->tempConfig, &options->moduleArgs);
120        FF_STRBUF_AUTO_DESTROY timeStr = ffStrbufCreate();
121        if (result->timeRemaining > 0) {
122            ffDurationAppendNum((uint32_t) result->timeRemaining, &timeStr);
123        }
124
125        FF_LIST_AUTO_DESTROY status = ffListCreate();
126        if (result->status & FF_BATTERY_STATUS_AC_CONNECTED) {
127            ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "AC Connected");
128        }
129        if (result->status & FF_BATTERY_STATUS_USB_CONNECTED) {
130            ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "USB Connected");
131        }
132        if (result->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) {
133            ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Wireless Connected");
134        }
135        if (result->status & FF_BATTERY_STATUS_CHARGING) {
136            ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Charging");
137        }
138        if (result->status & FF_BATTERY_STATUS_DISCHARGING) {
139            ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Discharging");
140        }
141        if (result->status & FF_BATTERY_STATUS_CRITICAL) {
142            ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Critical");
143        }
144        if (result->status & FF_BATTERY_STATUS_UNKNOWN) {
145            ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Unknown");
146        }
147
148        FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {
149                                                                                                     FF_ARG(result->manufacturer, "manufacturer"),
150                                                                                                     FF_ARG(result->modelName, "model-name"),
151                                                                                                     FF_ARG(result->technology, "technology"),
152                                                                                                     FF_ARG(capacityNum, "capacity"),
153                                                                                                     FF_ARG(status, "status"),
154                                                                                                     FF_ARG(tempStr, "temperature"),
155                                                                                                     FF_ARG(result->cycleCount, "cycle-count"),
156                                                                                                     FF_ARG(result->serial, "serial"),
157                                                                                                     FF_ARG(result->manufactureDate, "manufacture-date"),
158                                                                                                     FF_ARG(capacityBar, "capacity-bar"),
159                                                                                                     FF_ARG(days, "time-days"),
160                                                                                                     FF_ARG(hours, "time-hours"),
161                                                                                                     FF_ARG(minutes, "time-minutes"),
162                                                                                                     FF_ARG(seconds, "time-seconds"),
163                                                                                                     FF_ARG(timeStr, "time-formatted"),
164                                                                                                 }));
165    }
166}
167
168bool ffPrintBattery(FFBatteryOptions* options) {
169    FF_LIST_AUTO_DESTROY results = ffListCreate();
170
171    const char* error = ffDetectBattery(options, &results);
172
173    if (error) {
174        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Battery), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
175        return false;
176    }
177    if (results.length == 0) {
178        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Battery), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", "No batteries found");
179        return false;
180    }
181
182    for (uint32_t i = 0; i < results.length; i++) {
183        FFBatteryResult* result = FF_LIST_GET(FFBatteryResult, results, i);
184        printBattery(options, result, results.length == 1 ? 0 : (uint8_t) (i + 1));
185    }
186
187    FF_LIST_FOR_EACH (FFBatteryResult, result, results) {
188        ffStrbufDestroy(&result->manufacturer);
189        ffStrbufDestroy(&result->modelName);
190        ffStrbufDestroy(&result->technology);
191        ffStrbufDestroy(&result->serial);
192        ffStrbufDestroy(&result->manufactureDate);
193    }
194    return true;
195}
196
197void ffParseBatteryJsonObject(FFBatteryOptions* options, yyjson_val* module) {
198    yyjson_val *key, *val;
199    size_t idx, max;
200    yyjson_obj_foreach (module, idx, max, key, val) {
201        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) {
202            continue;
203        }
204
205        if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig)) {
206            continue;
207        }
208
209        if (ffPercentParseJsonObject(key, val, &options->percent)) {
210            continue;
211        }
212
213        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Battery), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
214    }
215}
216
217[[gnu::cold]]
218void ffGenerateBatteryJsonConfig(FFBatteryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
219    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
220
221    ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig);
222    ffPercentGenerateJsonConfig(doc, module, options->percent);
223}
224
225bool ffGenerateBatteryJsonResult(FFBatteryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
226    FF_LIST_AUTO_DESTROY results = ffListCreate();
227
228    const char* error = ffDetectBattery(options, &results);
229    if (error) {
230        yyjson_mut_obj_add_str(doc, module, "error", error);
231        return false;
232    }
233
234    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result");
235
236    FF_LIST_FOR_EACH (FFBatteryResult, battery, results) {
237        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);
238        yyjson_mut_obj_add_real(doc, obj, "capacity", battery->capacity);
239        yyjson_mut_obj_add_strbuf(doc, obj, "manufacturer", &battery->manufacturer);
240        yyjson_mut_obj_add_strbuf(doc, obj, "manufactureDate", &battery->manufactureDate);
241        yyjson_mut_obj_add_strbuf(doc, obj, "modelName", &battery->modelName);
242        yyjson_mut_obj_add_strbuf(doc, obj, "technology", &battery->technology);
243        yyjson_mut_obj_add_strbuf(doc, obj, "serial", &battery->serial);
244        if (battery->temperature != FF_BATTERY_TEMP_UNSET) {
245            yyjson_mut_obj_add_real(doc, obj, "temperature", battery->temperature);
246        } else {
247            yyjson_mut_obj_add_null(doc, obj, "temperature");
248        }
249        yyjson_mut_obj_add_uint(doc, obj, "cycleCount", battery->cycleCount);
250        if (battery->timeRemaining > 0) {
251            yyjson_mut_obj_add_int(doc, obj, "timeRemaining", battery->timeRemaining);
252        } else {
253            yyjson_mut_obj_add_null(doc, obj, "timeRemaining");
254        }
255        yyjson_mut_val* status = yyjson_mut_obj_add_arr(doc, obj, "status");
256        if (battery->status & FF_BATTERY_STATUS_AC_CONNECTED) {
257            yyjson_mut_arr_add_str(doc, status, "AC Connected");
258        }
259        if (battery->status & FF_BATTERY_STATUS_USB_CONNECTED) {
260            yyjson_mut_arr_add_str(doc, status, "USB Connected");
261        }
262        if (battery->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) {
263            yyjson_mut_arr_add_str(doc, status, "Wireless Connected");
264        }
265        if (battery->status & FF_BATTERY_STATUS_CHARGING) {
266            yyjson_mut_arr_add_str(doc, status, "Charging");
267        }
268        if (battery->status & FF_BATTERY_STATUS_DISCHARGING) {
269            yyjson_mut_arr_add_str(doc, status, "Discharging");
270        }
271        if (battery->status & FF_BATTERY_STATUS_CRITICAL) {
272            yyjson_mut_arr_add_str(doc, status, "Critical");
273        }
274        if (battery->status & FF_BATTERY_STATUS_UNKNOWN) {
275            yyjson_mut_arr_add_str(doc, status, "Unknown");
276        }
277    }
278
279    FF_LIST_FOR_EACH (FFBatteryResult, battery, results) {
280        ffStrbufDestroy(&battery->manufacturer);
281        ffStrbufDestroy(&battery->manufactureDate);
282        ffStrbufDestroy(&battery->modelName);
283        ffStrbufDestroy(&battery->technology);
284        ffStrbufDestroy(&battery->serial);
285    }
286
287    return true;
288}
289
290void ffInitBatteryOptions(FFBatteryOptions* options) {
291    ffOptionInitModuleArg(&options->moduleArgs, "");
292    options->temp = false;
293    options->tempConfig = (FFColorRangeConfig) { 60, 80 };
294    options->percent = (FFPercentageModuleConfig) { 50, 20, 0 };
295}
296
297void ffDestroyBatteryOptions(FFBatteryOptions* options) {
298    ffOptionDestroyModuleArg(&options->moduleArgs);
299}
300
301FFModuleBaseInfo ffBatteryModuleInfo = {
302    .name = "Battery",
303    .description = "Print battery information",
304    .displayName = {
305        .en = "Battery",
306        .ar = "البطارية",
307        .cs = "Baterie",
308        .de = "Akku",
309        .es = "Batería",
310        .fi = "Akku",
311        .fr = "Batterie",
312        .gl = "Batería",
313        .he = "סוללה",
314        .id = "Baterai",
315        .it = "Batteria",
316        .ja = "バッテリー",
317        .ko = "배터리",
318        .pl = "Bateria",
319        .pt = "Bateria",
320        .ru = "Батарея",
321        .tr = "Pil",
322        .uk = "Батарея",
323        .vi = "Pin",
324        .zh_CN = "电池",
325        .zh_TW = "電池",
326    },
327    .initOptions = (void*) ffInitBatteryOptions,
328    .destroyOptions = (void*) ffDestroyBatteryOptions,
329    .parseJsonObject = (void*) ffParseBatteryJsonObject,
330    .printModule = (void*) ffPrintBattery,
331    .generateJsonResult = (void*) ffGenerateBatteryJsonResult,
332    .generateJsonConfig = (void*) ffGenerateBatteryJsonConfig,
333    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
334        { "Battery manufacturer", "manufacturer" },
335        { "Battery model name *", "model-name" },
336        { "Battery technology", "technology" },
337        { "Battery capacity (percentage num)", "capacity" },
338        { "Battery status", "status" },
339        { "Battery temperature (formatted)", "temperature" },
340        { "Battery cycle count", "cycle-count" },
341        { "Battery serial number", "serial" },
342        { "Battery manufacture date", "manufacture-date" },
343        { "Battery capacity (percentage bar)", "capacity-bar" },
344        { "Battery time remaining days", "time-days" },
345        { "Battery time remaining hours", "time-hours" },
346        { "Battery time remaining minutes", "time-minutes" },
347        { "Battery time remaining seconds", "time-seconds" },
348        { "Battery time remaining (formatted)", "time-formatted" },
349    })),
350    .defaultOrder = 45,
351};