From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/modules/battery/battery.c | 326 ++++++++++++++++++++++++++++++++++++++++++ src/modules/battery/battery.h | 11 ++ src/modules/battery/option.h | 14 ++ 3 files changed, 351 insertions(+) create mode 100644 src/modules/battery/battery.c create mode 100644 src/modules/battery/battery.h create mode 100644 src/modules/battery/option.h (limited to 'src/modules/battery') diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c new file mode 100644 index 0000000..61cef84 --- /dev/null +++ b/src/modules/battery/battery.c @@ -0,0 +1,326 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/percent.h" +#include "common/duration.h" +#include "common/temps.h" +#include "common/strutil.h" +#include "detection/battery/battery.h" +#include "modules/battery/battery.h" + +static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uint8_t index) { + FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); + if (options->moduleArgs.key.length == 0) { + if (result->modelName.length > 0) { + ffStrbufSetF(&key, "%s (%s)", FF_BATTERY_MODULE_NAME, result->modelName.chars); + } else { + ffStrbufSetS(&key, FF_BATTERY_MODULE_NAME); + } + } else { + ffStrbufClear(&key); + FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { + FF_ARG(index, "index"), + FF_ARG(result->modelName, "name"), + FF_ARG(options->moduleArgs.keyIcon, "icon"), + })); + } + + FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; + + if (options->moduleArgs.outputFormat.length == 0) { + ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); + + FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); + bool showStatus = !(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT) && result->status; + + if (result->capacity >= 0) { + if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { + ffPercentAppendBar(&str, result->capacity, options->percent, &options->moduleArgs); + } + + if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { + if (str.length > 0) { + ffStrbufAppendC(&str, ' '); + } + + ffPercentAppendNum(&str, result->capacity, options->percent, str.length > 0, &options->moduleArgs); + } + + if (result->timeRemaining > 0) { + if (str.length > 0) { + ffStrbufAppendS(&str, " ("); + } + + ffDurationAppendNum((uint32_t) result->timeRemaining, &str); + ffStrbufAppendS(&str, " remaining)"); + } + } + + if (showStatus) { + bool hasOtherInfo = str.length > 0; + if (hasOtherInfo) { + ffStrbufAppendS(&str, " ["); + } + if (result->status & FF_BATTERY_STATUS_AC_CONNECTED) { + ffStrbufAppendS(&str, "AC Connected, "); + } + if (result->status & FF_BATTERY_STATUS_USB_CONNECTED) { + ffStrbufAppendS(&str, "USB Connected, "); + } + if (result->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) { + ffStrbufAppendS(&str, "Wireless Connected, "); + } + if (result->status & FF_BATTERY_STATUS_CHARGING) { + ffStrbufAppendS(&str, "Charging, "); + } + if (result->status & FF_BATTERY_STATUS_DISCHARGING) { + ffStrbufAppendS(&str, "Discharging, "); + } + if (result->status & FF_BATTERY_STATUS_CRITICAL) { + ffStrbufAppendS(&str, "Critical, "); + } + if (result->status & FF_BATTERY_STATUS_UNKNOWN) { + ffStrbufAppendS(&str, "Unknown, "); + } + ffStrbufSubstrBefore(&str, str.length - 2); // Remove last ", " + if (hasOtherInfo) { + ffStrbufAppendC(&str, ']'); + } + } + + if (result->temperature != FF_BATTERY_TEMP_UNSET) { + if (str.length > 0) { + ffStrbufAppendS(&str, " - "); + } + + ffTempsAppendNum(result->temperature, &str, options->tempConfig, &options->moduleArgs); + } + + ffStrbufPutTo(&str, stdout); + } else { + uint32_t timeRemaining = result->timeRemaining < 0 ? 0 : (uint32_t) result->timeRemaining; + uint32_t seconds = timeRemaining % 60; + timeRemaining /= 60; + uint32_t minutes = timeRemaining % 60; + timeRemaining /= 60; + uint32_t hours = timeRemaining % 24; + timeRemaining /= 24; + uint32_t days = timeRemaining; + + FF_STRBUF_AUTO_DESTROY capacityNum = ffStrbufCreate(); + if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { + ffPercentAppendNum(&capacityNum, result->capacity, options->percent, false, &options->moduleArgs); + } + FF_STRBUF_AUTO_DESTROY capacityBar = ffStrbufCreate(); + if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { + ffPercentAppendBar(&capacityBar, result->capacity, options->percent, &options->moduleArgs); + } + FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate(); + ffTempsAppendNum(result->temperature, &tempStr, options->tempConfig, &options->moduleArgs); + FF_STRBUF_AUTO_DESTROY timeStr = ffStrbufCreate(); + if (result->timeRemaining > 0) { + ffDurationAppendNum((uint32_t) result->timeRemaining, &timeStr); + } + + FF_STRBUF_AUTO_DESTROY statusStr = ffStrbufCreate(); + if (result->status & FF_BATTERY_STATUS_AC_CONNECTED) { + ffStrbufAppendS(&statusStr, "AC Connected, "); + } + if (result->status & FF_BATTERY_STATUS_USB_CONNECTED) { + ffStrbufAppendS(&statusStr, "USB Connected, "); + } + if (result->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) { + ffStrbufAppendS(&statusStr, "Wireless Connected, "); + } + if (result->status & FF_BATTERY_STATUS_CHARGING) { + ffStrbufAppendS(&statusStr, "Charging, "); + } + if (result->status & FF_BATTERY_STATUS_DISCHARGING) { + ffStrbufAppendS(&statusStr, "Discharging, "); + } + if (result->status & FF_BATTERY_STATUS_CRITICAL) { + ffStrbufAppendS(&statusStr, "Critical, "); + } + if (result->status & FF_BATTERY_STATUS_UNKNOWN) { + ffStrbufAppendS(&statusStr, "Unknown, "); + } + ffStrbufSubstrBefore(&statusStr, statusStr.length - 2); // Remove last ", " + + FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { + FF_ARG(result->manufacturer, "manufacturer"), + FF_ARG(result->modelName, "model-name"), + FF_ARG(result->technology, "technology"), + FF_ARG(capacityNum, "capacity"), + FF_ARG(statusStr, "status"), + FF_ARG(tempStr, "temperature"), + FF_ARG(result->cycleCount, "cycle-count"), + FF_ARG(result->serial, "serial"), + FF_ARG(result->manufactureDate, "manufacture-date"), + FF_ARG(capacityBar, "capacity-bar"), + FF_ARG(days, "time-days"), + FF_ARG(hours, "time-hours"), + FF_ARG(minutes, "time-minutes"), + FF_ARG(seconds, "time-seconds"), + FF_ARG(timeStr, "time-formatted"), + })); + } +} + +bool ffPrintBattery(FFBatteryOptions* options) { + FF_LIST_AUTO_DESTROY results = ffListCreate(); + + const char* error = ffDetectBattery(options, &results); + + if (error) { + ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); + return false; + } + if (results.length == 0) { + ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", "No batteries found"); + return false; + } + + for (uint32_t i = 0; i < results.length; i++) { + FFBatteryResult* result = FF_LIST_GET(FFBatteryResult, results, i); + printBattery(options, result, results.length == 1 ? 0 : (uint8_t) (i + 1)); + } + + FF_LIST_FOR_EACH (FFBatteryResult, result, results) { + ffStrbufDestroy(&result->manufacturer); + ffStrbufDestroy(&result->modelName); + ffStrbufDestroy(&result->technology); + ffStrbufDestroy(&result->serial); + ffStrbufDestroy(&result->manufactureDate); + } + return true; +} + +void ffParseBatteryJsonObject(FFBatteryOptions* options, yyjson_val* module) { + yyjson_val *key, *val; + size_t idx, max; + yyjson_obj_foreach (module, idx, max, key, val) { + if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) { + continue; + } + + if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig)) { + continue; + } + + if (ffPercentParseJsonObject(key, val, &options->percent)) { + continue; + } + + ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); + } +} + +void ffGenerateBatteryJsonConfig(FFBatteryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); + + ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig); + ffPercentGenerateJsonConfig(doc, module, options->percent); +} + +bool ffGenerateBatteryJsonResult(FFBatteryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY results = ffListCreate(); + + const char* error = ffDetectBattery(options, &results); + if (error) { + yyjson_mut_obj_add_str(doc, module, "error", error); + return false; + } + + yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); + + FF_LIST_FOR_EACH (FFBatteryResult, battery, results) { + yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); + yyjson_mut_obj_add_real(doc, obj, "capacity", battery->capacity); + yyjson_mut_obj_add_strbuf(doc, obj, "manufacturer", &battery->manufacturer); + yyjson_mut_obj_add_strbuf(doc, obj, "manufactureDate", &battery->manufactureDate); + yyjson_mut_obj_add_strbuf(doc, obj, "modelName", &battery->modelName); + yyjson_mut_obj_add_strbuf(doc, obj, "technology", &battery->technology); + yyjson_mut_obj_add_strbuf(doc, obj, "serial", &battery->serial); + if (battery->temperature != FF_BATTERY_TEMP_UNSET) { + yyjson_mut_obj_add_real(doc, obj, "temperature", battery->temperature); + } else { + yyjson_mut_obj_add_null(doc, obj, "temperature"); + } + yyjson_mut_obj_add_uint(doc, obj, "cycleCount", battery->cycleCount); + if (battery->timeRemaining > 0) { + yyjson_mut_obj_add_int(doc, obj, "timeRemaining", battery->timeRemaining); + } else { + yyjson_mut_obj_add_null(doc, obj, "timeRemaining"); + } + yyjson_mut_val* status = yyjson_mut_obj_add_arr(doc, obj, "status"); + if (battery->status & FF_BATTERY_STATUS_AC_CONNECTED) { + yyjson_mut_arr_add_str(doc, status, "AC Connected"); + } + if (battery->status & FF_BATTERY_STATUS_USB_CONNECTED) { + yyjson_mut_arr_add_str(doc, status, "USB Connected"); + } + if (battery->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) { + yyjson_mut_arr_add_str(doc, status, "Wireless Connected"); + } + if (battery->status & FF_BATTERY_STATUS_CHARGING) { + yyjson_mut_arr_add_str(doc, status, "Charging"); + } + if (battery->status & FF_BATTERY_STATUS_DISCHARGING) { + yyjson_mut_arr_add_str(doc, status, "Discharging"); + } + if (battery->status & FF_BATTERY_STATUS_CRITICAL) { + yyjson_mut_arr_add_str(doc, status, "Critical"); + } + if (battery->status & FF_BATTERY_STATUS_UNKNOWN) { + yyjson_mut_arr_add_str(doc, status, "Unknown"); + } + } + + FF_LIST_FOR_EACH (FFBatteryResult, battery, results) { + ffStrbufDestroy(&battery->manufacturer); + ffStrbufDestroy(&battery->manufactureDate); + ffStrbufDestroy(&battery->modelName); + ffStrbufDestroy(&battery->technology); + ffStrbufDestroy(&battery->serial); + } + + return true; +} + +void ffInitBatteryOptions(FFBatteryOptions* options) { + ffOptionInitModuleArg(&options->moduleArgs, ""); + options->temp = false; + options->tempConfig = (FFColorRangeConfig) { 60, 80 }; + options->percent = (FFPercentageModuleConfig) { 50, 20, 0 }; +} + +void ffDestroyBatteryOptions(FFBatteryOptions* options) { + ffOptionDestroyModuleArg(&options->moduleArgs); +} + +FFModuleBaseInfo ffBatteryModuleInfo = { + .name = FF_BATTERY_MODULE_NAME, + .description = "Print battery information", + .initOptions = (void*) ffInitBatteryOptions, + .destroyOptions = (void*) ffDestroyBatteryOptions, + .parseJsonObject = (void*) ffParseBatteryJsonObject, + .printModule = (void*) ffPrintBattery, + .generateJsonResult = (void*) ffGenerateBatteryJsonResult, + .generateJsonConfig = (void*) ffGenerateBatteryJsonConfig, + .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { + { "Battery manufacturer", "manufacturer" }, + { "Battery model name", "model-name" }, + { "Battery technology", "technology" }, + { "Battery capacity (percentage num)", "capacity" }, + { "Battery status", "status" }, + { "Battery temperature (formatted)", "temperature" }, + { "Battery cycle count", "cycle-count" }, + { "Battery serial number", "serial" }, + { "Battery manufacture date", "manufacture-date" }, + { "Battery capacity (percentage bar)", "capacity-bar" }, + { "Battery time remaining days", "time-days" }, + { "Battery time remaining hours", "time-hours" }, + { "Battery time remaining minutes", "time-minutes" }, + { "Battery time remaining seconds", "time-seconds" }, + { "Battery time remaining (formatted)", "time-formatted" }, + })) +}; diff --git a/src/modules/battery/battery.h b/src/modules/battery/battery.h new file mode 100644 index 0000000..16ad027 --- /dev/null +++ b/src/modules/battery/battery.h @@ -0,0 +1,11 @@ +#pragma once + +#include "option.h" + +#define FF_BATTERY_MODULE_NAME "Battery" + +bool ffPrintBattery(FFBatteryOptions* options); +void ffInitBatteryOptions(FFBatteryOptions* options); +void ffDestroyBatteryOptions(FFBatteryOptions* options); + +extern FFModuleBaseInfo ffBatteryModuleInfo; diff --git a/src/modules/battery/option.h b/src/modules/battery/option.h new file mode 100644 index 0000000..d65fd1f --- /dev/null +++ b/src/modules/battery/option.h @@ -0,0 +1,14 @@ +#pragma once + +#include "common/option.h" +#include "common/percent.h" + +typedef struct FFBatteryOptions { + FFModuleArgs moduleArgs; + + bool temp; + FFColorRangeConfig tempConfig; + FFPercentageModuleConfig percent; +} FFBatteryOptions; + +static_assert(sizeof(FFBatteryOptions) <= FF_OPTION_MAX_SIZE, "FFBatteryOptions size exceeds maximum allowed size"); -- cgit v1.2.3